<template>
  <div
    class="order_preview"
    v-loading="percentageBool"
    :element-loading-text="nocontainer"
    element-loading-background="#f6f6f4"
    id="order_preview"
    style="position: relative"
  >
    <div
      :key="loadingDialogKey"
      v-show="loadingDialogShow"
      v-loading="calcLoading"
      :element-loading-text="calcLoadingText"
      class="occlu_loading"
      style="
        position: absolute;
        width: 200px;
        height: 130px;
        left: calc(50% - 100px);
        top: calc(50% - 50px);
        background: red;
      "
    ></div>
    <div class="not_data_box" v-if="modelLoadingError">
      <img style="width: 250px; height: 155px" src="@/assets/img/home/zanwushuju.png" />
      <div style="letter-spacing: 1px; margin-top: -20px">{{ $t('add.add1002') }}...</div>
    </div>
    <div v-if="!isDownloadAgain && !modelLoadingError">
      <div v-if="!percentageBool" class="order_top_box">
        <div class="model_box" v-if="!percentageBool && isModel" :span="4" style="z-index: 99">
          <el-card class="model_switch_box">
            <h3>{{ $t('caseorder.yulan.yulan129') }}</h3>
            <div class="model_switch_item" v-for="(item, index) in fileListArr" :key="index">
              <el-tooltip :content="item.typeName" placement="top">
                <span style="max-width: 280px">{{ item.typeName }}</span>
              </el-tooltip>

              <el-switch v-model="item.show" active-color="#003a64" @change="modelSwitch(item)" />
            </div>
          </el-card>
        </div>
        <!-- 框选模型图片展示区域 -->
        <el-card class="pic_box" v-show="currentClickType == 'Icamera'">
          <h3>{{ $t('caseorder.anli127') }}</h3>
          <img v-show="!viewfinderPic" src="@/assets/img/upperjaw.png" />

          <div
            :class="['pic_contcnt_box']"
            :style="{ 'min-height': viewfinderPic ? '240px' : '0px' }"
          >
            <!--  -->

            <img
              @dblclick="picAction('3')"
              :style="{ filter: `brightness(${brightness})` }"
              :src="viewfinderPic"
              style="width: 100%"
              v-show="isPic"
            />

            <!-- 操作按钮 -->
            <div class="pic_action_box" v-show="viewfinderPic">
              <!-- 增加亮度 -->
              <el-tooltip :content="$t('caseorder.anli130')" placement="top">
                <div
                  :class="[
                    'pic_action',
                    'pic_action_1',
                    brightness <= 1.5 && !isInvalid ? 'pic_action_active' : '',
                  ]"
                  @click="picAction('1')"
                >
                  <img
                    v-show="brightness <= 1.5 && !isInvalid"
                    src="@/assets/img/icon/Brightness increase_1.png"
                    alt=""
                  />
                  <img
                    v-show="brightness > 1.5 || isInvalid"
                    src="@/assets/img/icon/Brightness increase_3.png"
                    alt=""
                  />
                </div>
              </el-tooltip>

              <!-- 降低亮度 -->
              <el-tooltip :content="$t('caseorder.anli131')" placement="top">
                <div
                  :class="[
                    'pic_action',
                    'pic_action_2',
                    brightness >= 0.5 && !isInvalid ? 'pic_action_active' : '',
                  ]"
                  @click="picAction('2')"
                >
                  <img
                    v-show="brightness >= 0.5 && !isInvalid"
                    src="@/assets/img/icon/Decreased brightness_1.png"
                    alt=""
                  />
                  <img
                    v-show="brightness < 0.5 || isInvalid"
                    src="@/assets/img/icon/Decreased brightness_3.png"
                    alt=""
                  />
                </div>
              </el-tooltip>

              <el-tooltip :content="$t('caseorder.anli132')" placement="top">
                <!-- 全屏 -->
                <div
                  :class="['pic_action', 'pic_action_3', !isInvalid ? 'pic_action_active' : '']"
                  @click="picAction('3')"
                >
                  <img v-show="!isInvalid" src="@/assets/img/icon/amplify_1.png" alt="" />
                  <img v-show="isInvalid" src="@/assets/img/icon/amplify_2.png" alt="" />
                </div>
              </el-tooltip>
            </div>
          </div>
          <p>{{ $t('caseorder.anli134') }}</p>
        </el-card>

        <div
          class="order_preview_btns"
          ref="order_preview_btns"
          :style="{ opacity: picBoxFull ? '0.5' : '1' }"
        >
          <!-- 咬合检测 -->
          <el-tooltip :content="$t('oralScan.oralScan_138')" placement="bottom">
            <span
              :class="{
                texture_btn_click_bg: currentClickType == 'occlu',
                order_preview_item: true,
              }"
              style="position: absolute; left: -155px"
            >
              <img v-show="!isPrepareOcclu" src="@/assets/img/icon/view_occlu_disabled.png" alt />
              <img
                v-show="isPrepareOcclu && currentClickType != 'occlu'"
                @click="enterOcclusalFun()"
                src="@/assets/img/icon/view_occlu.png"
                alt
              />
              <img
                v-show="isPrepareOcclu && currentClickType == 'occlu'"
                @click="exitOcclusalFun()"
                src="@/assets/img/icon/view_occlu_active.png"
                alt
              />
            </span>
          </el-tooltip>
          <!-- 倒凹检测 -->
          <el-tooltip :content="$t('oralScan.oralScan_139')" placement="bottom">
            <span
              :class="{
                texture_btn_click_bg: currentClickType == 'undercut',
                order_preview_item: true,
              }"
              style="position: absolute; left: -105px"
            >
              <img
                v-if="!isPrepareUndercut"
                src="@/assets/img/icon/view_undercut_disabled.png"
                alt
              />
              <img
                v-else-if="isPrepareUndercut && currentClickType != 'undercut'"
                @click="enterUndercutFun()"
                src="@/assets/img/icon/view_undercut.png"
                alt
              />
              <img
                v-show="isPrepareUndercut && currentClickType == 'undercut'"
                @click="exitUndercutFun()"
                src="@/assets/img/icon/view_undercut_active.png"
                alt
              />
            </span>
          </el-tooltip>
          <!-- 切换纹理部分 -->
          <div
            class="switch_texture_box"
            @mouseenter="hoverTextureBtn"
            @mouseleave="leaveTextureBtn"
          >
            <el-tooltip
              v-if="$route.path !== '/OrderPreview'"
              :content="$t('caseorder.anli124')"
              placement="left"
            >
              <span
                @click="switchTexture('1')"
                :class="['btn_bg_item', 'btn_bg_item_1', `${isSelectFirstIcon()}`]"
              >
                <img
                  v-show="showTextureBtn && currtexturIndex === '1'"
                  src="@/assets/img/icon/no_texture_3.png"
                  alt=""
                />
                <img
                  v-show="showTextureBtn && currtexturIndex !== '1'"
                  src="@/assets/img/icon/no_texture_1.png"
                  alt=""
                />
                <img
                  v-show="!showTextureBtn && currtexturIndex === '1'"
                  src="@/assets/img/icon/no_texture_3.png"
                  alt=""
                />
                <img
                  v-show="!showTextureBtn && currtexturIndex === '2'"
                  src="@/assets/img/icon/color_texture_3.png"
                  alt=""
                />
                <img
                  v-show="!showTextureBtn && currtexturIndex === '3'"
                  src="@/assets/img/icon/texture_mapping_3.png"
                  alt=""
                />
              </span>
            </el-tooltip>
            <el-tooltip
              v-if="$route.path !== '/OrderPreview'"
              :content="$t('caseorder.anli125')"
              placement="left"
            >
              <span
                @click="switchTexture('2')"
                :class="[
                  'btn_bg_item',
                  'btn_bg_item_2',
                  `${currtexturIndex === '2' ? 'texture_btn_click_bg' : 'texture_btn_normal_bg'}`,
                ]"
                v-show="showTextureBtn"
              >
                <img
                  v-show="modelType !== 'stl' && currtexturIndex !== '2'"
                  src="@/assets/img/icon/color_texture_1.png"
                  alt=""
                />
                <img
                  v-show="modelType !== 'stl' && currtexturIndex === '2'"
                  src="@/assets/img/icon/color_texture_3.png"
                  alt=""
                />
                <img
                  v-show="modelType === 'stl'"
                  src="@/assets/img/icon/color_texture_2.png"
                  alt=""
                />
              </span>
            </el-tooltip>
            <el-tooltip
              v-if="$route.path !== '/OrderPreview'"
              :content="$t('caseorder.anli126')"
              placement="left"
            >
              <span
                @click="switchTexture('3')"
                :class="[
                  'btn_bg_item',
                  'btn_bg_item_3',
                  `${currtexturIndex === '3' ? 'texture_btn_click_bg' : 'texture_btn_normal_bg'}`,
                ]"
                v-show="showTextureBtn"
              >
                <img
                  v-show="modelType !== 'stl' && currtexturIndex !== '3'"
                  src="@/assets/img/icon/texture_mapping_1.png"
                  alt=""
                />
                <img
                  v-show="modelType !== 'stl' && currtexturIndex === '3'"
                  src="@/assets/img/icon/texture_mapping_3.png"
                  alt=""
                />
                <img
                  v-show="modelType === 'stl'"
                  src="@/assets/img/icon/texture_mapping_2.png"
                  alt=""
                />
              </span>
            </el-tooltip>
          </div>
          <!-- 口内相机 -->
          <el-tooltip
            v-if="$route.path !== '/OrderPreview'"
            :content="$t('caseorder.anli127')"
            placement="bottom"
          >
            <span
              :class="[
                'btn_bg_item',
                'btn_bg_item_1',
                `${
                  currentClickType == 'Icamera' ? 'texture_btn_click_bg' : 'texture_btn_normal_bg'
                }`,
              ]"
            >
              <img
                v-show="
                  oralCameraDisplay === 'true' &&
                  currentClickType == 'Icamera' &&
                  !intraoralCameraDisabled
                "
                @click="exitOralFun()"
                src="@/assets/img/icon/intraoral_camera_3.png"
                alt=""
              />
              <img
                v-show="
                  oralCameraDisplay === 'true' &&
                  currentClickType != 'Icamera' &&
                  !intraoralCameraDisabled
                "
                @click="enterOralFun()"
                src="@/assets/img/icon/intraoral_camera_1.png"
                alt=""
              />
              <img
                v-show="oralCameraDisplay === 'false' || intraoralCameraDisabled"
                src="@/assets/img/icon/intraoral_camera_2.png"
                alt=""
              />
            </span>
          </el-tooltip>
          <el-tooltip
            v-if="$route.path !== '/OrderPreview'"
            :content="isFullScreen ? $t('caseorder.yulan.yulan12') : $t('caseorder.yulan.yulan13')"
            placement="bottom"
          >
            <span class="order_preview_item" @click="fullScreen">
              <img v-show="!isFullScreen" src="@/assets/img/order_banQ.png" alt />
              <img v-show="isFullScreen" src="@/assets/img/order_banQ1.png" alt />
            </span>
          </el-tooltip>
          <!-- 刷新 -->
          <el-tooltip :content="$t('caseorder.yulan.yulan14')" placement="bottom">
            <span class="order_preview_item" @click="resetAngle">
              <img src="@/assets/img/order_banJiaoHuan.png" alt />
            </span>
          </el-tooltip>
          <!-- 抵消双击模型按钮冒泡被选中 -->
          <el-tooltip>
            <div style="width: 1px; height: 1px"></div>
          </el-tooltip>
        </div>
      </div>
      <el-row class="order_model_box">
        <div
          v-show="isPrepareOcclu && currentClickType == 'occlu'"
          class="OralReport_model_box bd"
          style="position: absolute; left: calc(50% - 35px); z-index: 99; bottom: 20px"
        >
          <div class="btn_box">
            <el-tooltip
              class="item"
              effect="dark"
              :content="$t('oralScan.oralScan_100')"
              placement="top"
            >
              <img
                @click="switchView('1')"
                :class="{ themeBg: viewAngle == '1' }"
                :src="viewAngle == '1' ? btn_imgs.occlu_active : btn_imgs.occlu"
                alt=""
              />
            </el-tooltip>

            <el-tooltip
              class="item"
              effect="dark"
              :content="$t('oralReport.view_angle_3')"
              placement="top"
            >
              <img
                @click="switchView('3')"
                :class="{ themeBg: viewAngle === '3' }"
                :src="viewAngle === '3' ? btn_imgs.occlu_fill_active : btn_imgs.occlu_fill"
                alt=""
              />
            </el-tooltip>
          </div>
        </div>
        <div
          v-show="isPrepareUndercut && currentClickType == 'undercut'"
          class="OralReport_model_box bd"
          style="position: absolute; left: calc(50% - 35px); z-index: 99; bottom: 20px"
        >
          <div class="btn_box">
            <el-tooltip class="item" effect="dark" :content="$t('add.add1003')" placement="top">
              <img
                @click="smearFun()"
                :class="{ themeBg: viewAngle == '1' && !undercutPaintBtnDis }"
                :src="
                  undercutPaintBtnDis
                    ? btn_imgs.undercut_active_disabled
                    : viewAngle == '1'
                    ? btn_imgs.undercut_active
                    : btn_imgs.undercut
                "
                alt=""
              />
            </el-tooltip>
            <el-tooltip class="item" effect="dark" :content="$t('add.add1004')" placement="top">
              <img
                @click="clearSmearHandle()"
                :class="{ themeBg: viewAngle === '3' }"
                :src="viewAngle === '3' ? btn_imgs.undercut_reset_active : btn_imgs.undercut_reset"
                alt=""
              />
            </el-tooltip>
          </div>
        </div>
        <!-- 3d内容 -->
        <div class="order_preview_inner">
          <div id="container" ref="container" @click="createSelectionBox">
            <div
              :class="{
                circle: true,
                'smear-circle': currentClickType == 'undercut',
              }"
              ref="circle"
              v-show="
                (['undercut'].includes(currentClickType) && viewAngle == 1) ||
                ['Icamera'].includes(currentClickType)
              "
            ></div>
          </div>
        </div>
      </el-row>

      <div class="not_data_box" v-if="!isModel">
        <img style="width: 250px; height: 155px" src="@/assets/img/home/zanwushuju.png" />
        <div style="letter-spacing: 1px; margin-top: -20px">{{ $t('add.add4') }}...</div>
      </div>
    </div>

    <div class="not_data_box" v-else-if="isDownloadAgain && !modelLoadingError">
      <img style="width: 250px; height: 155px" src="@/assets/img/home/zanwushuju.png" />
      <div style="letter-spacing: 1px; margin-top: -20px">
        <span class="overdueText">{{ $t('add.add066') }}</span>
        <span class="loadBtn" @click="toLoadModel()">{{ $t('add.add067') }}</span>
      </div>
    </div>
    <ColorList
      v-if="currentClickType == 'occlu'"
      :colorVal="colorVal"
      @change="(val) => getColor(val)"
      style="position: absolute; bottom: 35px"
    />
    <Undercut
      v-if="currentClickType == 'undercut' && isOpenUndercut"
      :colorVal="colorVal"
      @change="(val) => getColor(val)"
      style="position: absolute; bottom: 35px"
    />
    <div
      v-if="currentClickType == 'undercut' && isOpenUndercut"
      :class="{
        undercut_eg: true,
        [isCurrentLang]: true,
      }"
    >
      <p class="title">{{ $t('caseorder.yulan.yulan147') }}</p>
      <div class="content">
        {{ $t('caseorder.yulan.yulan148') }}
      </div>
      <div class="undercut_eg_img_box">
        <img class="undercut_eg_img" src="@/assets/img/icon/undercut_eg.png" alt />
      </div>
    </div>
    <div v-if="showMouseNum" class="ctm-popover">
      <div
        class="point"
        style="
          position: absolute;
          width: 10px;
          height: 10px;
          border-radius: 5px;
          border: 1px solid #ccc;
          pointer-events: none; /* 不影响点击事件 */
        "
      ></div>
      <div
        class="first-line"
        style="position: absolute; width: 60px; height: 2px; background: white"
      ></div>
      <div
        class="second-line"
        style="position: absolute; width: 100px; height: 2px; background: white; rotate: 300deg"
      ></div>
      <div
        class="point-num"
        style="
          position: absolute;
          border: 1px solid #ccc;
          text-align: center;
          line-height: 20px;
          padding: 6px;
          border-radius: 4px;
          background: #fff;
        "
      >
        {{ currentDistance }}um
      </div>
    </div>
  </div>
</template>

<script>
import * as THREE from 'three';
import JSZip from 'jszip';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';
import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { ArcballControls } from '@/modelTransformation/ArcballControls';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OBJExporter, PLYExporter, STLExporter, GLTFExporter } from 'three-stdlib';
import Cookies from 'js-cookie';
import { cloneDeep } from 'lodash';
import '@/assets/scss/preview.scss';
const scanRodLastName = [
  'upperImplant.blzmesh',
  'lowerImplant.blzmesh',
  'upperjaw-edentulous-implant.blzmesh',
  'lowerjaw-edentulous-implant.blzmesh',
];
const upperLangeNames = ['上颌', 'upperjaw', 'maxilar'];
const lowerLangeNames = ['下颌', 'lowerjaw', 'mandíbula'];
import {
  vertexShader,
  fragmentShader,
  vertexShaderDistance,
  fragmentShaderDistance,
} from '@/config/3D';

import { createLight, switchCamera, disposeAll, setCamera } from './threeBasicConfig.js';

import {
  setMarkGeometry,
  clearSmearFun,
  groupingMtlData,
  processingColdData,
  markFacesInRadius_imp,
  projectMesh2PointsInGLCoordHandle,
  writeFlatVec3ArrayToMemory,
  updateProjectedPoints,
} from './util.js';

import ColorList from '@/components/color';
import Undercut from '@/components/color/undercut';

import { setTimeout } from 'core-js';
let openaxes = false;
let scene = new THREE.Scene();

var controls; // 优化效率
let upperCasePosition = []; //上颌顶点数组
let lowerCasePosition = []; //下颌顶点数组

let upperCaseIndices = []; //上颌面数组
let lowerCaseIndices = []; //下颌面数组
let initializedModule = null;

let upperDistancesOcclu = [];
let lowerDistancesOcclu = [];
let upperDistancesUndercut = [];
let lowerDistancesUndercut = [];
const getUniformsPartParams = (texture = null) => {
  //有纹理时一套亮度，顶点颜色和无纹理一套亮度
  return {
    lightColor: { value: new THREE.Color(0xffffff) }, // 光源颜色（白色）
    ambientStrength: { value: texture ? 1.0 : 0.7 },
    diffcoef: { value: texture ? 0.0 : 0.4 }, // 调大可让漫反射更明显（接近真实牙齿）
    specularStrength: { value: texture ? 0.6 : 0.1 }, // 镜面反射强度（牙齿高光不宜过强）
  };
};
let UnderCutionUpper = null;
let UnderCutionLower = null;
let UnderCutionUpperSetDataOK = false;
let UnderCutionLowerSetDataOK = false;
let brushMeshes = [];
let mouseMoveIntersects = [];

let coneGeometryMesh = null; //就位道mesh
let mvpInfo = {};
let projectedUpperPoints = []; // 存储上颌顶点的投影点
let projectedLowerPoints = []; // 存储上颌顶点的投影点
let wasmPtsMemory = null;
let wasmMVP_VP = null;
let curUpperHighlight = []; //涂抹半径内上颌顶点索引集合
let curLowerHighlight = []; //涂抹半径内下颌顶点索引集合

let upperPaintArray = [];
let lowerPaintArray = [];

let needsUpdateProjection = false;
export default {
  name: 'preview',
  components: { ColorList,
Undercut },
  data() {
    return {
      hadOccluActive: false,
      percentageBool: true,
      originalFileListArr: [],
      fileListArr: [],
      // 单独存储上下两个牙齿
      toothFileListArr: [],
      nocontainer: this.$t('oralReport.oralReport_1'),
      // 3d模型部分
      camera: {
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
      }, // 相机
      renderer: null, // 渲染器
      mesh: null, // 网格

      //point: null,
      uploadlist: [],
      imageFilelist: [],
      geometryData: null,
      materialData: null,
      // 是否显示纹理
      isShowTexture: true,
      // 是否是正交视角
      isPerspective: true,
      // 控制模型的透明度
      modelOpacity: 100,
      // 标志是否是全屏状态
      isFullScreen: false,
      dialogVisible: false,
      //groups: [],

      uploadPercentage: 0,
      planGroup: {},
      // 确定是否有模型
      isModel: true,
      // 确定是否需要重新下载
      isDownloadAgain: false,

      spotLight: {},

      shaderMaterial: null,
      originalSize: new THREE.Vector3(),
      animationFrameId: null,
      mouseMoveTimerOcclu: null, // 添加这一行用于存储鼠标移动的定时器
      isMouseMoving: false, // 添加这一行用于跟踪鼠标是否在移动
      needsCoordinateUpdate: false, // 添加这一行用于跟踪
      // // 定义一个变量控制是否显示切换纹理另外的两个按钮
      showTextureBtn: false,
      // // 记录当前点击的纹理
      currtexturIndex: '',

      // 保存原始的材质
      materialPlyMap: {},
      // // 定义一个变量存储模型类型
      modelType: '',
      // 定义一个变量是否是gltf
      isGltfModel: false,
      // 定义口内相机图标是否显示
      clickOralIcon: false,
      boxes: [], // 存储当前框选区域
      // 存储取景框的图片
      viewfinderPic: '',
      // 存储图片的亮度
      brightness: 1,
      // 记录图片盒子是否全屏
      picBoxFull: false,

      // raycaster: null,
      // 定义一个变量确定点击亮度增加 减少选中样式
      brightnessIndex: '',
      // 判断模型是否全部加载完毕
      isModelLoaded: false,
      time1: null,
      time2: null,
      // 确定是否是新图片
      isPic: false,
      id: '',
      isOrderDownload: false,
      previewType: '',
      oralCameraDisplay: false,
      //记录是否是无效位置
      isInvalid: false,
      // 存储Blzmesh模型的mesh数据
      allLineList: [],
      // 根据配置文件txt存在与否判断口内相机是否禁用，txt文件不存在则禁用,由客户端控制
      intraoralCameraDisabled: true,
      orderManagement: null,
      previewOrder: null,

      currentGeometry: null,
      isPrepareOcclu: false, //是否开启咬合（同时有上下颌才能咬合）
      isPrepareUndercut: false, //是否能开启倒凹（有上or下任何一个即可开启）
      // clickedOcclu: false, //咬合检测是否被点击
      currentClickType: 'none', //0：无点击，1：咬合，2：倒凹,3:口内相机
      currentDistance: 0, //咬合距离
      // pickableObjects: [], // 缓存可见的可交互对象
      colorVal: 1000, // 颜色值(咬合距离)
      viewAngle: '1', // 视角(1:前视，3：平铺)
      btn_imgs: {
        occlu: require('@/assets/img/icon/view_occlu.png'),
        occlu_active: require('@/assets/img/icon/view_occlu_active.png'),
        occlu_fill: require('@/assets/img/icon/occlu_fill_order.png'),
        occlu_fill_active: require('@/assets/img/icon/occlu_fill_active_order.png'),

        undercut: require('@/assets/img/icon/view_undercut_order.png'),
        undercut_active: require('@/assets/img/icon/view_undercut_order_active.png'),
        undercut_active_disabled: require('@/assets/img/icon/view_undercut_order_disabled.png'),
        undercut_reset: require('@/assets/img/icon/undercut_reset_order.png'),
        undercut_reset_active: require('@/assets/img/icon/undercut_reset_active_order.png'),
      },

      showMouseNum: false, // 是否显示测量值
      calcLoading: false,
      calcLoadingText: this.$t('add.add1001'),
      loadingDialogShow: false,
      modeFileType: '', //模型文件类型
      modelLoadingError: false, //模型加载失败
      loadingDialogKey: 0,
      isHasUndercutDis: false, //是否有倒凹计算结果
      isOpenUndercut: false, //是否开启倒凹
      leftMouseKeyDown: false, //鼠标左键被按下
      undercutPaintBtnDis: false, //涂抹按钮是否可用
    };
  },
  created() {
    this.getparams();
    this.messageInit();
    this.isCurrentLang = localStorage.getItem('lang');
    (async () => {
      const moduleUrl = '/MeshAlgorithm.js';
      const matrixUrl = '/projPts.wasm';

      try {
        // 加载 JS 模块
        const wasmModule = await import(/* webpackIgnore: true */ moduleUrl);
        initializedModule = await wasmModule.default();

        const go = new Go();

        // 方法 1：先尝试流式加载（如果服务器支持正确的 MIME）
        try {
          const result = await WebAssembly.instantiateStreaming(
            fetch(matrixUrl, {
              headers: { Accept: 'application/wasm' }, // 尝试告诉服务器需要 WASM 类型
            }),
            go.importObject,
          );
          go.run(result.instance);
          console.log('WASM 模块加载成功（流式加载）');
        } catch (streamingError) {
          console.warn('流式加载失败，回退到手动加载:', streamingError);

          // 方法 2：手动 fetch + arrayBuffer（兼容错误的 MIME）
          const response = await fetch(matrixUrl);
          if (!response.ok) throw new Error(`WASM 下载失败: ${response.status}`);

          const wasmBuffer = await response.arrayBuffer();
          const result = await WebAssembly.instantiate(wasmBuffer, go.importObject);

          go.run(result.instance);
          console.log('WASM 模块加载成功（手动加载）');
        }

        // 获取 WASM 内存引用
        if (window.wasmMemory && window.wasmMVP_VP) {
          wasmPtsMemory = window.wasmMemory;
          wasmMVP_VP = window.wasmMVP_VP;
        }

        console.log('WASM 内存已获取:', wasmPtsMemory, wasmMVP_VP);
      } catch (err) {
        console.error('WASM 加载失败:', err);
      }
    })();
  },
  mounted() {
    const container = this.$refs.container;
    this.currentMesh = null;
    this.point = null;
    this.groups = [];

    this.blzmeshMeshs = [];

    this.pickableObjects = []; // 缓存可见的可交互对象

    this.raycaster = new THREE.Raycaster();

    const element = document.getElementById('container');
    const resizeObserver = new ResizeObserver((entries) => {
      for (let entry of entries) {
        // 检查元素的宽度是否发生变化
        this.$nextTick(() => {
          // this.onWindowResize();
          // 在这里可以执行任何你需要的操作
        });
      }
    });
    resizeObserver.observe(element);

    // 获取传递过来的值
    this.getiframeData();
    container.addEventListener('contextmenu', (e) => e.preventDefault());
    // 自定义 XMLHttpRequest 拦截
  },

  methods: {
    getHotOrCodeStatus() {
      if (
        ((this.previewOrder?.markStatus == 0 ||
          ([1, 2].includes(this.previewOrder?.markStatus) &&
            this.previewOrder?.unfreezeStatus == 1)) &&
          this.orderManagement == 'false') ||
        ((this.previewOrder?.backMarkStatus == 0 ||
          ([1, 2].includes(this.previewOrder?.backMarkStatus) &&
            this.previewOrder?.backUnfreezeStatus == 1)) &&
          this.orderManagement == 'true')
      ) {
        this.initModel();
      } else if (
        (this.orderManagement == 'false' && this.previewOrder?.markStatus == 3) ||
        (this.orderManagement == 'true' && this.previewOrder?.backMarkStatus == 3)
      ) {
        this.percentageBool = true;
        this.isDownloadAgain = true;
        this.nocontainer = this.$t('caseorder.yulan.yulan139');
      } else if (
        (this.orderManagement == 'false' && this.previewOrder?.markStatus == 4) ||
        (this.orderManagement == 'true' && this.previewOrder?.markStatus == 4)
      ) {
        this.percentageBool = true;
        this.isDownloadAgain = true;
        this.nocontainer = this.$t('caseorder.yulan.yulan140');
      } else {
        this.percentageBool = false;
        this.isDownloadAgain = true;
      }
    },
    messageInit() {
      var languageType = localStorage.getItem('lang');
      if (languageType === undefined || languageType === null) {
        languageType = 'cn';
      }
      this.$http
        .get(
          `/order/selectDetail?id=${
            this.$route.query.id
          }&languageType=${languageType.toString()}&token=${Cookies.get('token')}&timeZone=${
            this.$timeZone
          }`,
        )
        .then(({ data: res }) => {
          if (res.code !== 0) {
            return this.$message.error(res.msg);
          } else {
            this.previewOrder = res.data.order;
            this.getHotOrCodeStatus();
          }
        });
    },
    getparams() {
      let url = window.location.href;
      // 1. 获取查询字符串部分
      const queryString = url.split('?')[1]; // 获取 ? 后面的部分

      // 2. 将查询字符串按 "&" 分割成各个参数
      const params = queryString.split('&');

      // 3. 遍历每个参数，获取键和值
      params.forEach((param) => {
        const [key, value] = param.split('=');
        this[key] = value || '';
      });
      window.addEventListener('message', (event) => {
        let data = event.data;
        if (data.type === 'refreshCircle') {
          this.$nextTick(() => {
            this.updateRender();
            this.createCircle();
          });
        }
        if (data.type === 'refreshBrightness') {
          this.brightness = data.data;
        }
        if (data.type === 'refreshPicBoxFull') {
          this.picBoxFull = data.data;
        }
        if (data.type === 'updateRender') {
          this.updateRender();

          if (data.orderCodeToHot) {
            this.nocontainer = data.orderCodeToHot;
            this.percentageBool = true;
            this.messageInit();
          }
        }
      });
    },

    // 定义一个方法确定第一个图标是否被选中
    isSelectFirstIcon() {
      let str = 'texture_btn_normal_bg';
      if (this.showTextureBtn) {
        if (this.currtexturIndex === '1') {
          str = 'texture_btn_click_bg';
        }
      } else {
        str = 'texture_btn_click_bg';
      }
      return str;
    },

    createCircle() {
      if (this.mouseMoveHandler) {
        window.removeEventListener('mousemove', this.mouseMoveHandler);
      }

      const container = this.$refs.container;
      const circle = this.$refs.circle;

      // 定义事件处理函数并保存引用
      this.mouseMoveHandler = (event) => {
        // 计算鼠标相对于容器的坐标，包含滚动偏移
        const containerRect = container.getBoundingClientRect();
        const scrollX = window.scrollX || document.documentElement.scrollLeft;
        const scrollY = window.scrollY || document.documentElement.scrollTop;

        const mouseX = event.pageX - (containerRect.left + scrollX);
        const mouseY = event.pageY - (containerRect.top + scrollY);

        // 限制圆圈在容器内
        const containerWidth = containerRect.width;
        const containerHeight = containerRect.height;
        const circleWidth = circle.offsetWidth;
        const circleHeight = circle.offsetHeight;

        const circleX = Math.min(Math.max(mouseX, 0), containerWidth - circleWidth);
        const circleY = Math.min(Math.max(mouseY, 0), containerHeight - circleHeight);

        // 更新圆圈的位置（让圆圈的中心点对齐鼠标）
        circle.style.left = `${circleX - circleWidth / 2}px`;
        circle.style.top = `${circleY - circleHeight / 2}px`;
      };
      window.addEventListener('mousemove', this.mouseMoveHandler);
    },
    // 创建框选区域
    createSelectionBox(event) {
      if (this.currentClickType != 'Icamera') {
        return false;
      }
      this.isInvalid = false;
      this.isPic = false;

      let needArr = this.fileListArr.filter((f) => f.show === true);

      if (needArr && needArr.length !== 1) {
        return this.$message({
          message: this.$t('caseorder.anli128'),
          type: 'warning',
        });
      }
      if (!this.camera) {
        return false;
      }

      const mouse = new THREE.Vector2();
      let dom = this.renderer.domElement;
      const rect = dom.getBoundingClientRect();
      mouse.x = ((event.clientX - rect.left) / dom.clientWidth) * 2 - 1;
      mouse.y = -((event.clientY - rect.top) / dom.clientHeight) * 2 + 1;

      mouse.set(mouse.x, mouse.y);
      this.raycaster.setFromCamera(mouse, this.camera);
      let pickableObjects = [];

      this.group.children.forEach((item) => {
        if (this.isGltfModel) {
          if (this.previewType === 'ply') {
            if (item.children[0].visible) {
              pickableObjects.push(item);
            }
          } else {
            if (item.children && item.children.length > 0) {
              if (item.children[0].visible) {
                pickableObjects.push(item);
              }
            }
          }
        } else {
          if (this.modelType === 'stl' || this.modelType === 'ply') {
            if (item.visible) {
              pickableObjects.push(item);
            }
          } else {
            if (item.visible) {
              pickableObjects.push(item);
            }
          }
        }
      });

      let intersects = this.raycaster.intersectObjects(pickableObjects, true);

      intersects = intersects.filter((f) => f.object.visible === true);

      if (intersects.length > 0) {
        const intersectedObject = intersects[0];

        let fileType;
        let targetLine = intersectedObject.faceIndex;
        let orderId = this.id ? this.id : this.$route.query.id;

        if (needArr[0].typeName === lowerLangeNames[0]) {
          fileType = 'lowerjaw';
        } else if (needArr[0].typeName === upperLangeNames[0]) {
          fileType = 'upperjaw';
        } else if (needArr[0].typeName === upperLangeNames[1]) {
          fileType = 'upperjaw';
        } else if (needArr[0].typeName === lowerLangeNames[2]) {
          fileType = 'lowerjaw';
        } else if (needArr[0].typeName === lowerLangeNames[2]) {
          fileType = 'lowerjaw';
        } else if (needArr[0].typeName === upperLangeNames[2]) {
          fileType = 'upperjaw';
        }
        const enumList = {
          upperjaw: 'upper',
          lowerjaw: 'lower',
        };
        this.setViewfinderPic(enumList[fileType], targetLine);
      } else {
        this.$message.error(this.$t('caseorder.anli129'));
        this.isInvalid = true;
      }
    },
    setViewfinderPic(type, targetLine) {
      this.isPic = true;
      this.viewfinderPic =
        sessionStorage.getItem('oralCameraFilePrefix') +
        type +
        '_' +
        this.allLineList[targetLine] +
        '.jpg'; //获取图片url

      this.brightness = 1;
    },
    getConfigTxt(tagItem, callback) {
      const textName = lowerLangeNames.includes(tagItem.typeName) ? 'lower' : 'upper';
      const prefixUrl = sessionStorage.getItem('oralCameraFilePrefix');
      const configUrl = `${prefixUrl}${textName}.txt`;

      try {
        fetch(`${configUrl}?_t=${Math.random()}`)
          .then((response) => {
            return response.text();
          })
          .then((res) => {
            if (
              res.indexOf('The specified key does not exist') !== -1 ||
              res.indexOf('NoSuchKey') !== -1
            ) {
              this.intraoralCameraDisabled = true;
              return;
            }
            this.intraoralCameraDisabled = false;
            this.allLineList = Object.freeze(res.split(/\r?\n/));
            callback && callback();
          })
          .catch((error) => {
            console.log(error, 'error');
          });
      } catch (error) {
        console.error('Invalid URL:', error);
      }
    },
    hoverTextureBtn() {
      this.showTextureBtn = true;
    },

    leaveTextureBtn() {
      this.showTextureBtn = false;
    },
   
    IsHasDisArray() {
      if ('occlu' == this.currentClickType) {
        return (
          upperDistancesOcclu &&
          upperDistancesOcclu.length > 0 &&
          lowerDistancesOcclu &&
          lowerDistancesOcclu.length > 0
        );
      } else if ('undercut' == this.currentClickType) {
        return (
          (upperDistancesUndercut && upperDistancesUndercut.length > 0) ||
          (lowerDistancesUndercut && lowerDistancesUndercut.length > 0)
        );
      } else {
        return false;
      }
    },
    // 修改switchTexture方法
    switchTexture(type, distanColor) {
      this.currtexturIndex = type;

      const operationType =
        this.currentClickType === 'occlu' ? 1 : this.currentClickType === 'undercut' ? 2 : 1;

      // 选择着色器
      const [useVertexShader, useFragmentShader] = this.IsHasDisArray()
        ? [vertexShaderDistance, fragmentShaderDistance]
        : [vertexShader, fragmentShader];

      const distanceColor = distanColor !== undefined ? distanColor : this.colorVal / 1000;
      const setMaterialRecursive = (child, materialFn) => {
        if (child.parent && child.parent.texture) {
          child.texture = child.parent.texture;
        }
        if (child.isMesh) {
          materialFn(child);
        } else if (child.children && child.children.length > 0) {
          child.children.forEach((grandChild) => setMaterialRecursive(grandChild, materialFn));
        }
      };
      let materialSetter;
      switch (type) {
        case '1': // 无纹理
          materialSetter = (child) => {
            const isScanRod = scanRodLastName.some(
              (item) => child.meshUrl && child.meshUrl.endsWith(item),
            );
            const defaultColorValue = isScanRod
              ? new THREE.Vector3(150.0 / 255.0, 150.0 / 255.0, 154.0 / 255.0)
              : new THREE.Vector3(231.0 / 255.0, 223.0 / 255.0, 147.0 / 255.0);

            child.material = new THREE.ShaderMaterial({
              uniforms: {
                ...getUniformsPartParams(),
                uTexture: { value: null },
                useTexture: { value: false },
                useVertexColor: { value: false },
                lightPosition: { value: this.camera.position },
                renderType: { value: this.isGltfModel ? 1 : 2 },
                textureType: { value: -1 },
                defaultColor: { value: defaultColorValue },
                distanceColor: { value: distanceColor },
                useOperationType: { value: operationType },
              },
              side: THREE.DoubleSide,
              transparent: false,
              vertexShader: useVertexShader,
              fragmentShader: useFragmentShader,
            });
            child.material.needsUpdate = true;
          };
          break;

        case '2': // 颜色纹理
          materialSetter = (child) => {
            const isScanRod = scanRodLastName.some(
              (item) => child.meshUrl && child.meshUrl.endsWith(item),
            );
            const hasVertexColors = !isScanRod && child.geometry?.attributes?.color?.count > 0;

            const defaultColorValue = isScanRod
              ? new THREE.Vector3(150.0 / 255.0, 150.0 / 255.0, 154.0 / 255.0)
              : new THREE.Vector3(231.0 / 255.0, 223.0 / 255.0, 147.0 / 255.0);

            child.material = new THREE.ShaderMaterial({
              uniforms: {
                ...getUniformsPartParams(),
                uTexture: { value: null },
                useTexture: { value: false },
                useVertexColor: { value: hasVertexColors },
                useModeTypeValue: { value: this.modeFileType.includes('blzmesh') },
                lightPosition: { value: this.camera.position },
                renderType: { value: this.isGltfModel ? 1 : 2 },
                textureType: { value: hasVertexColors ? 0 : -1 },
                defaultColor: { value: defaultColorValue },
                distanceColor: { value: distanceColor },
                useOperationType: { value: operationType },
              },
              vertexShader: useVertexShader,
              fragmentShader: useFragmentShader,
              side: THREE.DoubleSide,
              transparent: false,
              depthWrite: true,
            });
            child.material.needsUpdate = true;
          };
          break;

        case '3': // 纹理
          materialSetter = (child) => {
            const isScanRod = scanRodLastName.some(
              (item) => child.meshUrl && child.meshUrl.endsWith(item),
            );
            const hasTexture = !!child.texture;
            const defaultColorValue = isScanRod
              ? new THREE.Vector3(150.0 / 255.0, 150.0 / 255.0, 154.0 / 255.0)
              : new THREE.Vector3(231.0 / 255.0, 223.0 / 255.0, 147.0 / 255.0);

            child.material = new THREE.ShaderMaterial({
              uniforms: {
                ...getUniformsPartParams(hasTexture),
                uTexture: { value: child.texture },
                useTexture: { value: hasTexture },
                useVertexColor: { value: false },
                lightPosition: { value: this.camera.position },
                renderType: { value: 2 },
                textureType: { value: 0 },
                defaultColor: { value: defaultColorValue },
                distanceColor: { value: distanceColor },
                useOperationType: { value: operationType },
              },
              vertexShader: useVertexShader,
              fragmentShader: useFragmentShader,
              side: THREE.DoubleSide,
            });
            child.material.needsUpdate = true;
          };
          break;

        default:
          return;
      }

      // 应用材质到所有组
      this.groups.forEach((child) => setMaterialRecursive(child, materialSetter));

      this.renderer.render(scene, this.camera);
      this.showTextureBtn = false;
    },
    getiframeData() {
      this.id = this.$route.query.id;
      this.orderManagement = this.$route.query.orderManagement;
      this.isOrderDownload = 'true';
      this.previewType = this.$route.query.previewType;
      this.oralCameraDisplay = this.$route.query.oralCameraDisplay;
    },
    initModel() {
      this.getFileList();

      window.addEventListener('resize', () => {
        this.updateRender();
      });
      window.addEventListener('click', (event) => {
        window.parent.postMessage(
          {
            type: 'cancellationRoller', // 自定义事件的类型
          },
          '*',
        );
      });
    },

    clearModel() {
      if (scene !== null && scene.children.length > 0) {
        scene.children.pop();
        // 必须要清空当前div下的canvas不然canvas会继续叠加
        const domDiv = document.getElementById('container');
        if (domDiv) {
          while (domDiv.firstChild) {
            domDiv.removeChild(domDiv.firstChild);
          }
        }
        // ...重置相关data变量，例如camera，light...
        scene = null;
        controls = null;
        this.camera = {
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
        }; // 相机
        this.renderer = null; // 渲染器
        this.mesh = null; // 网格
      }
    },
    // 拿3d文件
    getFileList() {
      this.groups = [];

      let param = {
        orderId: this.id ? this.id : this.$route.query.id,
      };
      if (this.$route.query.manufacturerCode) {
        param['manufacturerCode'] = this.$route.query.manufacturerCode;
      }
      // 这里做个判断，如果是全屏的预览就是另外一个接口
      let url = '/order/select3DFileList';
      if (this.$route.path === '/OrderPreview') {
        url = '/order/select3DFileListThirdParty';
      }

      this.$http({
        url: url,
        method: 'post',
        data: param,
      })
        .then(({ data: res }) => {
          if (res.code !== 0) {
            this.percentageBool = false;
            this.isModel = false;
            return this.$message.error(res.msg);
          } else {
            const containsGltf = res.data.some(
              (item) => item.url && item.url.split('.').pop() === 'gltf',
            );
            const containsBlzmesh = res.data.some(
              (item) => item.url && item.url.split('.').pop() === 'blzmesh',
            );
            if (containsBlzmesh) {
              groupingMtlData(this, res.data, false, 'blzmesh');
            } else {
              if (containsGltf) {
                // gltf模型
                groupingMtlData(this, res.data, false, 'gltf');
              } else {
                // obj模型
                groupingMtlData(this, res.data, true);
              }
            }

            // 临时
            if (this.$route.query.manufacturerCode) {
              this.isDownloadAgain = false;
            } else {
              processingColdData(this, res.data[0].fileStatusModel);
            }

            if (this.isDownloadAgain) {
              return false;
            }

            if (this.fileListArr.length > 0) {
              this.isModel = true;

              this.group = new THREE.Object3D();
              this.$nextTick(() => {
                this.clearModel();
                this.init();
              });

              this.time1 = setTimeout(() => {
                const startTime = new Date().getTime(); // 记录开始时间
                const hasUpper = this.fileListArr?.some((item) =>
                  item.url.endsWith('upperjaw.blzmesh'),
                );
                const hasLower = this.fileListArr?.some((item) =>
                  item.url.endsWith('lowerjaw.blzmesh'),
                );
                this.isPrepareOcclu = !!hasUpper && !!hasLower;
                this.isPrepareUndercut = !!hasUpper || !!hasLower;
                let promises = this.fileListArr.map((item) => {
                  return new Promise((resolve, reject) => {
                    if (item.url && item.url.includes('.stl')) {
                      this.modelType = 'stl';
                      this.isGltfModel = false;
                      this.currtexturIndex = '1';
                      this.modeFileType = 'stl';
                      this.loadSTL(item.url, item.typeName, item.picUrl, resolve); // 加载STL模型
                    } else if (item.url && item.url.includes('.ply')) {
                      this.modelType = 'ply';
                      this.isGltfModel = false;
                      this.currtexturIndex = '3';
                      this.modeFileType = 'ply';

                      this.loadPLY(item.url, item.typeName, item.picUrl, resolve); // 加载PLY模型
                    } else if (item.url && item.url.includes('.obj')) {
                      this.modelType = 'obj';
                      this.isGltfModel = false;
                      this.currtexturIndex = '3';
                      this.modeFileType = 'obj';

                      this.loadOBJ(item.url, item.typeName, item.mtlUrl, item.picUrl, resolve);
                    } else if (item.url && item.url.includes('.gltf')) {
                      this.isGltfModel = true;
                      this.modeFileType = 'gltf';

                      this.loadGltf(item.url, item.mtlUrl, item.picUrl, item.typeName, resolve);
                    } else if (item.url && item.url.includes('.blzmesh')) {
                      // blzmesh
                      this.modelType = this.previewType;
                      this.isGltfModel = false;
                      this.modeFileType = 'blzmesh';

                      // let url = item.url;
                      // if (item.typeName == '上颌') {
                      //   url = '/blzapi/upperjaw.blzmesh';
                      // } else {
                      //   url = '/blzapi/lowerjaw.blzmesh';
                      // }
                      this.loadBlzmesh(item.url, item.typeName, resolve);
                    }
                  });
                });

                // 使用 Promise.all 确保所有模型都加载完毕
                Promise.all(promises)
                  .then(() => {
                    this.modelLoadingError = false;

                    this.groups.forEach((mesh) => {
                      const defValues = new Float32Array(
                        mesh.geometry?.attributes?.position?.count,
                      ).fill(-999999.9);
                      mesh.geometry?.setAttribute(
                        'fHighlight',
                        new THREE.BufferAttribute(defValues, 1),
                      );
                      mesh.geometry?.setAttribute(
                        'disValue',
                        new THREE.BufferAttribute(defValues, 1),
                      );
                    });

                    this.groups.forEach((item) => {
                      item.position.x = -222; // X轴上移动-2单位
                      item.position.y = 223; // Y轴上移动3单位
                      this.group.add(item);
                    });
                    this.group.scale.set(1, 1, 1); // 确保模型的缩放为1
                    this.time2 = setTimeout(() => {
                      scene.add(this.group);

                      this.group.rotateY((-89.6 / 180) * Math.PI);
                      this.group.rotateX((-89.6 / 180) * Math.PI);
                      // // 计算组的中心位置
                      let boundingBox = new THREE.Box3().setFromObject(this.group);
                      this.originalSize = new THREE.Vector3();
                      this.originalSize.copy(boundingBox.getSize(new THREE.Vector3()));
                      let center = new THREE.Vector3();

                      boundingBox.getCenter(center);

                      // // 将组居中
                      this.group.position.sub(center);

                      this.camera.position.z = this.originalSize.length() * 1.5;

                      if (containsGltf) {
                        this.nocontainer = this.$t('add.add162');
                      } else {
                        this.nocontainer = this.$t('oralReport.oralReport_1') + '100%';
                      }

                      this.updateRender();
                      this.percentageBool = false;

                      const endTime = new Date().getTime();
                      const loadTime = (endTime - startTime) / 1000; // 转换为秒
                      this.isModelLoaded = true;
                      // this.$emit('showAction');

                      window.parent.postMessage(
                        {
                          type: 'showAction', // 自定义事件的类型
                        },
                        '*',
                      );
                    }, 500);
                  })
                  .catch((err) => {
                    this.percentageBool = false;
                    this.isModel = false;
                  })
                  .finally(() => {});
              }, 500);
            } else {
              this.isModel = false;
              this.percentageBool = false;
              // this.nocontainer = this.$t('add.add013');
              // this.$emit('showAction');
              window.parent.postMessage(
                {
                  type: 'showAction', // 自定义事件的类型
                },
                '*',
              );
            }
          }
        })
        .catch((err) => {
          this.percentageBool = false;
          this.isModel = false;
          console.log('err', err);
        });
    },
    // 处理gltf流程
    handleGltf(data) {
      groupingMtlData(this, data, false);
    },
    groupingData(data) {
      let res_data = [];
      let group = {};
      if (data && data.length > 0) {
        data.forEach((item) => {
          const parts = item.fileName.split(/\.(?=.+)/); // 使用正则表达式来分割最后一个点
          // 文件名
          let name = parts[0];
          // 后缀名
          let suffix = parts.slice(1).join('.');
          // 截取出中间固定的字符
          let str;
          if (suffix === 'png') {
            str = name.split('_')[0].split('-')[1];
          } else {
            str = name.split('-')[1];
          }
          if (group[str]) {
            if (suffix === 'png') {
              group[str].picUrl = item.url;
            } else if (suffix === 'obj') {
              group[str].url = item.url;
              group[str].typeName = item.typeName ? item.typeName : str;
            } else if (suffix === 'mtl') {
              group[str].mtlUrl = item.url;
            }
          } else {
            if (suffix === 'png') {
              let pigName = name.split('_')[0].split('-')[1];
              group[pigName] = {
                fileStatusModel: item.fileStatusModel,
                url: '',
                typeName: item.typeName ? item.typeName : str,
                mtlUrl: '',
                picUrl: item.url,
              };
            } else if (suffix === 'obj') {
              group[str] = {
                fileStatusModel: item.fileStatusModel,
                url: item.url,
                typeName: item.typeName ? item.typeName : str,
                mtlUrl: '',
                picUrl: '',
              };
            } else if (suffix === 'mtl') {
              group[str] = {
                fileStatusModel: item.fileStatusModel,
                url: '',
                typeName: item.typeName ? item.typeName : str,
                mtlUrl: item.url,
                picUrl: '',
              };
            }
          }
        });
      }
      Object.keys(group).forEach((key) => {
        res_data.push(group[key]);
      });
      return res_data;
    },
    splitLastDot(str) {
      var lastDotIndex = str.lastIndexOf('.');
      if (lastDotIndex === -1) return [str, ''];

      return str.slice(lastDotIndex + 1);
    },

    // 初始化
    init() {
      if (scene == null || scene.children.length < 3) {
        scene = new THREE.Scene();
        createLight(this, scene); // 创建光源
      }
      this.createCamera(); // 创建相机
      this.createRender(); // 创建渲染器
      this.createControls(); // 创建控件对象
      this.render(); // 渲染
    },
    // 添加鼠标事件监听器
    addMouseListeners() {
      if (this.pickableObjects.length === 0) {
        this.updatePickableObjects();
      }
      const domElement = this.renderer.domElement;
      domElement.addEventListener('mouseenter', this.onMouseEnterOcclu, false);
      domElement.addEventListener('mouseleave', this.onMouseLeaveOcclu, false);
      // 只监听鼠标点击事件，而不是移动事件
      domElement.addEventListener('mousemove', this.onMouseMoveOcclu, false);

      domElement.addEventListener('mousemove', this.onMouseMoveUndercut, false);
      domElement.addEventListener('dblclick', this.onDBclickUndercut, false);
      domElement.addEventListener('mousedown', this.onMouseDownUndercut, false);
      domElement.addEventListener('mouseup', this.onMouseUpUndercut, false);
      // 添加一个标志来跟踪鼠标是否在移动
      this.isMouseMoving = false;
      // 添加一个标志来跟踪是否需要更新坐标显示
      this.needsCoordinateUpdate = false;
    },
    setDataForUnderCut() {
      if (UnderCutionUpperSetDataOK && UnderCutionLowerSetDataOK) return;

      const curFileList = this.fileListArr.filter((e) => e.show);
      const UnderCut = initializedModule.UnderCut;
      UnderCutionUpper = new UnderCut();
      UnderCutionLower = new UnderCut();

      const copyToWasm = (data) => {
        const numBytes = data.length * data.BYTES_PER_ELEMENT;
        const ptr = initializedModule._malloc(numBytes);
        const heap = new Uint8Array(initializedModule.HEAPU8.buffer, ptr, numBytes);
        heap.set(new Uint8Array(data.buffer));
        return ptr;
      };
      let uVPtr = null;
      let uFPtr = null;
      let lVPtr = null;
      let lFPtr = null;
      const upperObj = curFileList.find((item) => upperLangeNames.includes(item.typeName));
      const lowerObj = curFileList.find((item) => lowerLangeNames.includes(item.typeName));
      if (upperObj && !UnderCutionUpperSetDataOK) {
        uVPtr = copyToWasm(upperCasePosition);
        uFPtr = copyToWasm(upperCaseIndices);
        UnderCutionUpper.setData(uVPtr, upperCasePosition.length, uFPtr, upperCaseIndices.length);
        UnderCutionUpperSetDataOK = true;
      }
      if (lowerObj && !UnderCutionLowerSetDataOK) {
        lVPtr = copyToWasm(lowerCasePosition);
        lFPtr = copyToWasm(lowerCaseIndices);
        UnderCutionLower.setData(lVPtr, lowerCasePosition.length, lFPtr, lowerCaseIndices.length);
        UnderCutionLowerSetDataOK = true;
      }
      uVPtr && initializedModule._free(uVPtr);
      uFPtr && initializedModule._free(uFPtr);
      lVPtr && initializedModule._free(lVPtr);
      lFPtr && initializedModule._free(lFPtr);
    },
    async onDBclickUndercut(event, isPaint) {
      if (this.currentClickType != 'undercut') {
        return false;
      }
      this.leftMouseKeyDown = false;
      this.undercutPaintBtnDis = true;
      this.leftMouseKeyDown = false;
      scene.remove(coneGeometryMesh);
      try {
        this.showMouseNum = true;
        const domElement = this.renderer.domElement;
        const rect = domElement.getBoundingClientRect();
        const mouseOccluPickPos = {
          x: ((event.clientX - rect.left) / rect.width) * 2 - 1,
          y: -((event.clientY - rect.top) / rect.height) * 2 + 1,
        };

        this.raycaster.setFromCamera(mouseOccluPickPos, this.camera);

        const intersects = this.raycaster.intersectObjects(this.pickableObjects, true);
        let viewVector = null;
        let hasIntersects = true;
        let point;
        if (intersects.length > 0) {
          // 如果有交点，则使用交点位置
          point = intersects[0].point;
        } else {
          // 如果没有交点，则在鼠标点击位置创建一个点
          // 使用鼠标在近裁剪面上的位置
          point = new THREE.Vector3(mouseOccluPickPos.x, mouseOccluPickPos.y, 0);
          point.unproject(this.camera);
          hasIntersects = false;
        }
        const cameraDirection = new THREE.Vector3();
        this.camera.getWorldDirection(cameraDirection);
        coneGeometryMesh = setMarkGeometry(
          this,
          point,
          cameraDirection.negate(),
          coneGeometryMesh,
          scene,
          hasIntersects,
        );
        const currentMeshPostion = this.camera.position.clone();
        currentMeshPostion.applyAxisAngle(new THREE.Vector3(0, 1, 0), (89.6 * Math.PI) / 180);
        // 绕X轴旋转165度
        currentMeshPostion.applyAxisAngle(new THREE.Vector3(1, 0, 0), (165 * Math.PI) / 180);

        this.camera.getWorldDirection(currentMeshPostion);

        viewVector = {
          x: currentMeshPostion.z,
          y: currentMeshPostion.x,
          z: currentMeshPostion.y,
        };

        this.loadingDialogKey = Math.random();
        this.loadingDialogShow = false;
        await this.$nextTick();
        this.calcLoading = true;
        this.calcLoadingText = this.$t('add.add1005');
        this.loadingDialogShow = true;

        setTimeout(() => {
          this.showMouseNum = false;
          let direction = new THREE.Vector3(); // 获取相机的世界方向
          this.camera.getWorldDirection(direction);

          const curFileList = this.fileListArr.filter((e) => e.show);

          const upperObj = curFileList.find((item) => upperLangeNames.includes(item.typeName));
          const lowerObj = curFileList.find((item) => lowerLangeNames.includes(item.typeName));

          if (upperObj && viewVector) {
            upperDistancesUndercut = UnderCutionUpper.calUnderCutDis([
              viewVector.x,
              viewVector.y,
              viewVector.z,
            ]);
            if (isPaint) {
              upperDistancesUndercut.map((_, index) => {
                if (upperPaintArray[index] != -1.0) {
                  upperDistancesUndercut[index] = 0;
                }
              });
            }
          }
          if (lowerObj && viewVector) {
            lowerDistancesUndercut = UnderCutionLower.calUnderCutDis([
              viewVector.x,
              viewVector.y,
              viewVector.z,
            ]);
            if (isPaint) {
              lowerDistancesUndercut.map((_, index) => {
                if (lowerPaintArray[index] != -1.0) {
                  lowerDistancesUndercut[index] = 0;
                }
              });
            }
          }
          this.isHasUndercutDis = true;

          // 计算倒凹距离
          if (upperObj) {
            this.setUndercutColor(upperDistancesUndercut, 'upper', this.colorVal / 1000);
          }
          if (lowerObj) {
            this.setUndercutColor(lowerDistancesUndercut, 'lower', this.colorVal / 1000);
          }

          this.calcLoading = false;
          this.loadingDialogShow = false;
        }, 50);
      } catch (error) {
        console.error('Error in getOcclusalFun:', error);
        this.calcLoading = false;
        this.loadingDialogShow = false;

        this.$message.error('咬合检测功能暂时不可用');
        throw error;
      }
    },
    onMouseDownUndercut(event) {
      if (this.undercutPaintBtnDis) {
        return false;
      }
      if (event.button == 0) {
        if (this.viewAngle == '1' && this.currentClickType == 'undercut') {
          this.leftMouseKeyDown = true;
          setTimeout(() => {
            this.undercutPaint(event);
          }, 0);
        }
      }
    },
    onMouseMoveUndercut(event) {
      if (this.leftMouseKeyDown) {
        setTimeout(() => {
          this.undercutPaint(event);
        }, 0);
        return;
      }

      // show dis label
      if (
        (!upperDistancesUndercut || upperDistancesUndercut.length == 0) &&
        (!lowerDistancesUndercut || lowerDistancesUndercut.length == 0)
      ) {
        return;
      }
      if (this.undercutPaintBtnDis) {
        this.pickAndShowDisLabel(event, upperDistancesUndercut, lowerDistancesUndercut);
      }
    },
    onMouseUpUndercut(event) {
      this.leftMouseKeyDown = false;
      if (event.button == 0) {
        if (
          this.viewAngle == '1' &&
          this.currentClickType == 'undercut' &&
          !this.undercutPaintBtnDis
        ) {
          this.leftMouseKeyDown = false;
          this.viewAngle = 0;
          this.groups.forEach((mesh) => {
            const defValues = new Float32Array(mesh.geometry.attributes.position.count).fill(0.0);
            mesh.geometry.setAttribute('fHighlight', new THREE.BufferAttribute(defValues, 1));
          });
          this.onDBclickUndercut(event, true);
        }
      }
    },
    undercutPaint(event) {
      const curViewMesh = this.group.children
        .map((mesh) => {
          if ([...upperLangeNames, ...lowerLangeNames].includes(mesh.typeName)) {
            return mesh;
          }
        })
        .filter((e) => Boolean(e));

      const domElement = this.renderer.domElement;
      const rect = domElement.getBoundingClientRect();
      const startTime = new Date();
      {
        const clientX = event.clientX;
        const clientY = event.clientY;

        curViewMesh.forEach((mesh) => {
          const paintValues = mesh.geometry.attributes.fHighlight.array;
          if ([...upperLangeNames].includes(mesh.typeName)) {
            const startTime = new Date();
            curUpperHighlight = this.markFacesInRadiusU(mesh, clientX, clientY, 30);

            curUpperHighlight?.forEach((index) => {
              paintValues[index] = -1.0;
            });
            upperPaintArray = paintValues;
          } else if ([...lowerLangeNames].includes(mesh.typeName)) {
            const startTime = new Date();
            curLowerHighlight = this.markFacesInRadiusL(mesh, clientX, clientY, 30);

            curLowerHighlight?.forEach((index) => {
              paintValues[index] = -1.0;
            });
            lowerPaintArray = paintValues;
          }

          mesh.geometry.attributes.fHighlight.needsUpdate = true;
        });
        this.switchTexture(this.currtexturIndex, this.colorVal / 1000);
      }
    },

    markFacesInRadiusL(mesh, clientX, clientY, radius) {
      return markFacesInRadius_imp(mesh, clientX, clientY, radius, projectedLowerPoints);
    },
    markFacesInRadiusU(mesh, clientX, clientY, radius) {
      return markFacesInRadius_imp(mesh, clientX, clientY, radius, projectedUpperPoints);
    },

    smearFun() {
      if (this.undercutPaintBtnDis) {
        return false;
      }
      if (this.viewAngle != 1) {
        this.viewAngle = 1;
        setTimeout(() => {
          needsUpdateProjection = true;
          this.calcUndercutProjPts();
        }, 10);
        setTimeout(() => {
          this.createCircle();
        }, 100);
      } else {
        this.viewAngle = 0;
        needsUpdateProjection = false;
        if (this.mouseMoveHandler) {
          const circle = this.$refs.circle;
          circle.style.left = '-999px';
          circle.style.top = '-999px';
          window.removeEventListener('mousemove', this.mouseMoveHandler);
          this.mouseMoveHandler = null;
        }
      }
    },
    //重置倒凹
    clearSmearHandle() {
      this.undercutPaintBtnDis = false;
      scene.remove(coneGeometryMesh);
      this.showMouseNum = false;
      this.setUndercutColor([], 'upper', this.colorVal / 1000);
      this.setUndercutColor([], 'lower', this.colorVal / 1000);
      upperDistancesUndercut = [];
      lowerDistancesUndercut = [];
      this.groups.forEach((mesh) => {
        const defValues = new Float32Array(mesh.geometry.attributes.position.count).fill(0.0);
        mesh.geometry.setAttribute('fHighlight', new THREE.BufferAttribute(defValues, 1));
        mesh.geometry.setAttribute('disValue', new THREE.BufferAttribute(defValues, 1));
      });
      clearSmearFun(
        this,
        upperLangeNames,
        lowerLangeNames,
        upperDistancesUndercut,
        lowerDistancesUndercut,
        scene,
      );
    },

    findClosestPointInFace(intersect) {
      if (!intersect.face) return;

      const positionAttribute = intersect.object.geometry.getAttribute('position');
      const face = intersect.face;

      // 获取面的三个顶点
      const vertices = [
        new THREE.Vector3(
          positionAttribute.getX(face.a),
          positionAttribute.getY(face.a),
          positionAttribute.getZ(face.a),
        ),
        new THREE.Vector3(
          positionAttribute.getX(face.b),
          positionAttribute.getY(face.b),
          positionAttribute.getZ(face.b),
        ),
        new THREE.Vector3(
          positionAttribute.getX(face.c),
          positionAttribute.getY(face.c),
          positionAttribute.getZ(face.c),
        ),
      ];

      // 计算鼠标点击位置到每个顶点的距离
      const distances = vertices.map((vertex) => intersect.point.distanceTo(vertex));

      // 找到最近的点
      const minDistance = Math.min(...distances);
      const closestVertexIndex = distances.indexOf(minDistance);

      // 确定点的索引
      let pointIndex;
      if (closestVertexIndex === 0) pointIndex = face.a;
      else if (closestVertexIndex === 1) pointIndex = face.b;
      else pointIndex = face.c;
    },

    // 鼠标点击事件处理（替代mousemove）
    onMouseMoveOcclu(event) {
      if ('occlu' != this.currentClickType) return;

      const currentUpperDis = upperDistancesOcclu;
      const currentLowerDis = lowerDistancesOcclu;

      if (!currentUpperDis || currentUpperDis.length == 0) return;
      if (!currentLowerDis || currentLowerDis.length == 0) return;
      if (this.currentClickType == 'occlu') {
        this.showMouseNum = true;
      }
      this.pickAndShowDisLabel(event, currentUpperDis, currentLowerDis);
    },
    pickAndShowDisLabel(event, currentUpperDis, currentLowerDis) {
      const domElement = this.renderer.domElement;
      const rect = domElement.getBoundingClientRect();
      const mouseOccluPickPos = {
        x: ((event.clientX - rect.left) / rect.width) * 2 - 1,
        y: -((event.clientY - rect.top) / rect.height) * 2 + 1,
      };

      this.raycaster.setFromCamera(mouseOccluPickPos, this.camera);

      const intersects = this.raycaster.intersectObjects(this.pickableObjects, true);
      const intersectsVisible = intersects.filter((v) => v.object.visible);

      if (intersectsVisible.length > 0) {
        const intersect = intersectsVisible[0];
        // 获取交点的索引信息
        // 面的三个顶点索引
        const vertexIndexA = intersect.face.a;
        const vertexIndexB = intersect.face.b;
        const vertexIndexC = intersect.face.c;

        // 相交的对象
        const object = intersect.object;

        // 获取交点坐标
        const intersectionPoint = intersect.point;
        // 获取顶点坐标
        const positionAttribute = intersect.object.geometry.getAttribute('position');

        // 计算三个顶点的坐标
        const vertexA = new THREE.Vector3(
          positionAttribute.getX(vertexIndexA),
          positionAttribute.getY(vertexIndexA),
          positionAttribute.getZ(vertexIndexA),
        );

        const vertexB = new THREE.Vector3(
          positionAttribute.getX(vertexIndexB),
          positionAttribute.getY(vertexIndexB),
          positionAttribute.getZ(vertexIndexB),
        );

        const vertexC = new THREE.Vector3(
          positionAttribute.getX(vertexIndexC),
          positionAttribute.getY(vertexIndexC),
          positionAttribute.getZ(vertexIndexC),
        );

        // 计算鼠标点击位置到每个顶点的距离
        const distanceToA = intersectionPoint.distanceTo(vertexA);
        const distanceToB = intersectionPoint.distanceTo(vertexB);
        const distanceToC = intersectionPoint.distanceTo(vertexC);

        // 找到最近的顶点索引
        let closestVertexIndex;
        if (distanceToA <= distanceToB && distanceToA <= distanceToC) {
          closestVertexIndex = vertexIndexA;
        } else if (distanceToB <= distanceToA && distanceToB <= distanceToC) {
          closestVertexIndex = vertexIndexB;
        } else {
          closestVertexIndex = vertexIndexC;
        }

        const upperLangeModelStatus = this.fileListArr.find((item) =>
          upperLangeNames.includes(item.typeName),
        )?.show;
        const lowerLangeModelStatus = this.fileListArr.find((item) =>
          lowerLangeNames.includes(item.typeName),
        )?.show;

        if (upperLangeNames.includes(object.typeName) && upperLangeModelStatus) {
          if (currentUpperDis && currentUpperDis.length > closestVertexIndex) {
            this.currentDistance = (currentUpperDis[closestVertexIndex] * 1000).toFixed(0); // 将距离转换为毫米并保留两位小数
          } else {
            this.currentDistance = null;
          }
        } else if (lowerLangeNames.includes(object.typeName) && lowerLangeModelStatus) {
          if (currentLowerDis && currentLowerDis.length > closestVertexIndex) {
            this.currentDistance = null;
            this.currentDistance = (currentLowerDis[closestVertexIndex] * 1000).toFixed(0); // 将距离转换为毫米并保留两位小数
          } else {
            this.currentDistance = null;
          }
        } else {
          this.currentDistance = null;
        }

        if (this.currentDistance <= this.colorVal && this.currentClickType == 'occlu') {
          this.setPopoverPosition(event);
          this.showMouseNum = true;
        } else if (this.currentDistance > 0 && this.currentClickType == 'undercut') {
          this.setPopoverPosition(event);
          this.showMouseNum = true;
        } else {
          this.showMouseNum = false;
        }
      } else {
        this.showMouseNum = false;
      }
    },

    updatePickableObjects() {
      if (this.group && this.group.children) {
        this.pickableObjects = this.group.children.filter((item) => item.visible); // 只保留可见的对象
      } else {
        this.pickableObjects = [];
      }
    },
    // 鼠标移动事件处理

    setPopoverPosition(event) {
      this.showMouseNum = true;
      const pointNode = document.getElementsByClassName('point')[0];
      const firstLine = document.getElementsByClassName('first-line')[0];
      const secondLine = document.getElementsByClassName('second-line')[0];
      const pointNum = document.getElementsByClassName('point-num')[0];
      this.$nextTick(() => {
        if (pointNode) {
          pointNode.style.left = `${event.clientX - 5}px`;
          pointNode.style.top = `${event.clientY - 5}px`;
        }
        if (firstLine) {
          firstLine.style.left = `${event.clientX + 5}px`;
          firstLine.style.top = `${event.clientY}px`;
          firstLine.style.background = '#00f';
        }
        if (secondLine) {
          secondLine.style.left = `${event.clientX + 40}px`;
          secondLine.style.top = `${event.clientY - 44}px`;
          secondLine.style.background = '#00f';
        }
        if (pointNum) {
          pointNum.style.left = `${event.clientX + 115}px`;
          pointNum.style.top = `${event.clientY - 104}px`;
        }
      });
    },
    // 鼠标进入容器事件处理
    onMouseEnterOcclu() {
      if (this.currentClickType != 'occlu') {
        //当前咬合检测打开
        return false;
      }
      this.updatePickableObjects();
    },

    // 鼠标离开容器事件处理
    onMouseLeaveOcclu() {
      if (this.currentClickType != 'occlu') {
        //当前咬合检测打开
        return false;
      }
    },

    // 创建相机
    createCamera() {
      switchCamera(this, controls, 'init');
    },
    // 创建渲染器
    createRender() {
      const element = document.getElementById('container');
      // preserveDrawingBuffer: true,//保留图形缓冲区
      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: false,
        preserveDrawingBuffer: true,
      });
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.renderer.setSize(element.clientWidth, element.clientHeight); // 设置渲染区域尺寸
      this.renderer.setPixelRatio(window.devicePixelRatio * 1); // 更新渲染器的像素比
      this.renderer.setClearColor(new THREE.Color('0xffffff'), 1.0); // 设置背景颜色
      element.appendChild(this.renderer.domElement);
    },
    // 创建控件对象
    createControls() {
      controls = new ArcballControls(this.camera, this.renderer.domElement, scene);
      controls.minDistance = 1; // 最小缩放距离，避免模型缩得太小
      controls.maxDistance = 500; // 最大缩放距离，避免模型缩得太远
      controls.minPolarAngle = 0; // 最小极角
      controls.maxPolarAngle = Math.PI; // 最大极角
      // controls.target.set(0, 0, 0); // 设置控制器的目标为模型的中心
      controls.setGizmosVisible(false);
      // controls.enablePan = false;
      // controls.enableDamping = true;
      controls.dampingFactor = 100;
      controls.disDBClick = true;
      controls.unsetMouseAction('FOV', 0);
      controls.setMouseAction('ZOOM', 1);
      controls.setMouseAction('ROTATE', 2);
      controls.unsetMouseAction('ROTATE', 0);
      controls.unsetMouseAction('0');
      controls.unsetMouseAction('ZOOM', 2);
      controls.setMouseAction('PAN', 1);

      // controls.setMouseAction('ROTATE', 0);
      controls.enablePan = true;
      if (openaxes) {
        const axesHelper = new THREE.AxesHelper(50);
        const colorsX = new THREE.Color(1, 0, 0);
        const colorsY = new THREE.Color(0, 1, 0);
        const colorsZ = new THREE.Color(0, 0, 1);
        axesHelper.setColors(colorsX, colorsY, colorsZ);

        scene.add(axesHelper);
      }

      let lastNeedsUpdateProjection = false;

      controls.addEventListener('start', () => {
        if (this.viewAngle == '1' && this.currentClickType == 'undercut') {
          this.smearFun();
        }
        if (
          this.viewAngle == '1' &&
          this.currentClickType == 'undercut' &&
          !this.undercutPaintBtnDis
        ) {
          lastNeedsUpdateProjection = true;
        }
        if (!this.isModelLoaded) {
          controls.enabled = false;
        } else {
          controls.enabled = true;
        }
      });
      let debounceTimer = null;
      controls.addEventListener('end', () => {
        window.parent.postMessage(
          {
            type: 'cancellationRoller', // 自定义事件的类型
          },
          '*',
        );
        if (debounceTimer) {
          clearTimeout(debounceTimer);
        }

        // 设置新的定时器，实现防抖功能
        debounceTimer = setTimeout(() => {
          if (lastNeedsUpdateProjection) {
            setTimeout(() => {
              lastNeedsUpdateProjection = false;
              needsUpdateProjection = true;
              this.calcUndercutProjPts();
            }, 10);
          }
          // 执行完成后清除引用
          debounceTimer = null;
        }, 500); // 300ms 的防抖延迟，可根据实际需求调整
      });

      let debounceTimerToResize = null;
      window.addEventListener('resize', () => {
        if (debounceTimerToResize) {
          clearTimeout(debounceTimerToResize);
        }
        debounceTimerToResize = setTimeout(() => {
          setTimeout(() => {
            if (
              this.viewAngle == '1' &&
              this.currentClickType == 'undercut' &&
              !this.undercutPaintBtnDis
            ) {
              lastNeedsUpdateProjection = true;
              this.calcUndercutProjPtsCondition(lastNeedsUpdateProjection);
            }
          }, 10);

          // 执行完成后清除引用
          debounceTimerToResize = null;
        }, 300); // 300ms 的防抖延迟，可根据实际需求调整
      });
    },
    calcUndercutProjPtsCondition(lastNeedsUpdateProjection) {
      if (lastNeedsUpdateProjection) {
        setTimeout(() => {
          lastNeedsUpdateProjection = false;
          needsUpdateProjection = true;
          this.calcUndercutProjPts();
        }, 10);
      }
    },
    // 加载STL模型
    loadSTL(url, typeName, picUrl, resolve) {
      const loader = new STLLoader();
      loader.load(
        url,
        (geometry) => {
          // 创建材质
          // geometry = new THREE.BoxGeometry(100, 100, 100);
          let texture = textureLoader.load(picUrl);
          // material.color.setStyle('rgb(231, 223, 147)'); //模型颜色
          // material.side = THREE.DoubleSide;
          this.mesh = new THREE.Mesh(geometry, material);
          this.mesh['typeName'] = typeName;
          // this.mesh.position.set(1, 1, 2)
          this.mesh.rotation.x = -0.5 * Math.PI;
          // this.mesh.scale.set(1.2, 1.2, 1.2);
          this[typeName] = this.mesh;
          this.groups.push(this.mesh);
          this.switchTexture(this.currtexturIndex, this.colorVal / 1000);
          resolve();
        },
        (event) => {
          this.calculateProgress(typeName, event, 'stl');
          // console.log("event---STL------>", event)
        },
        (onerror) => {
          this.percentageBool = false;
          this.isModel = false;
          this.modelLoadingError = true;
          console.log('err---STL-------->', onerror); // 报错信息在这一行代码
        },
      );
    },
    // 加载PLY模型  ./../../public/jawDie_0_1.ply
    loadPLY(url, typeName, picUrl, resolve) {
      const loader = new PLYLoader();
      // let model = require('../../../public/jawDie_0_1.ply')

      loader.load(
        url,
        (geometry) => {
          if (picUrl) {
            const textureLoader = new THREE.TextureLoader();
            let texture = textureLoader.load(picUrl);

            this.mesh = new THREE.Mesh(geometry);
            this.mesh.texture = texture;
            this.mesh.traverse((node) => {
              node['texture'] = texture;

              if (node.material) {
                if (node.geometry instanceof THREE.BufferGeometry) {
                  node.geometry.computeVertexNormals();
                  node.geometry.attributes.normal.needsUpdate = true;
                }
              }
            });
            this.mesh['typeName'] = typeName;
            this.mesh.ctmtexture = texture;
            this[typeName] = this.mesh;

            this.groups.push(this.mesh);
            this.switchTexture(this.currtexturIndex, this.colorVal / 1000);

            resolve();
          } else {
            const material = new THREE.MeshStandardMaterial({
              color: 0xffffff,
              flatShading: true,
            });
            material.side = THREE.DoubleSide;
            this.mesh = new THREE.Mesh(geometry, material);
            // this.mesh.rotation.x = -0.5 * Math.PI;
            // this.mesh.scale.set(1.2, 1.2, 1.2);
            this.mesh['typeName'] = typeName;
            this[typeName] = this.mesh;
            this.groups.push(this.mesh);
            resolve();
          }
        },
        (event) => {
          this.calculateProgress(typeName, event, 'ply');
          // console.log("ply---event--------->", event)
        },
        (onerror) => {
          this.percentageBool = false;
          this.isModel = false;
          this.modelLoadingError = true;
          console.log('err----------->', onerror); // 报错信息在这一行代码
        },
      );
    },
    // 生成纹理
    generateSprite() {
      const canvas = document.createElement('canvas');
      canvas.width = 16;
      canvas.height = 16;
      const context = canvas.getContext('2d');
      const gradient = context.createRadialGradient(
        canvas.width / 2,
        canvas.height / 2,
        0,
        canvas.width / 2,
        canvas.height / 2,
        canvas.width / 2,
      );
      gradient.addColorStop(0, 'rgba(255,255,255,1)');
      gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
      gradient.addColorStop(0.4, 'rgba(0,0,64,1)');
      gradient.addColorStop(1, 'rgba(0,0,0,1)');

      context.fillStyle = gradient;
      context.fillRect(0, 0, canvas.width, canvas.height);

      const texture = new THREE.Texture(canvas);
      texture.needsUpdate = true;
      return texture;
    },
    // 定义一个方法来计算模型加载进度
    calculateProgress(typeName, event, fileType) {
      let plan = 0;
      if (this.planGroup.hasOwnProperty(typeName)) {
        this.planGroup[typeName] = event.loaded / event.total;
      } else {
        this.planGroup[typeName] = '';
      }
      for (let key in this.planGroup) {
        plan += Number(this.planGroup[key]);
      }
      this.uploadPercentage = ((plan / this.fileListArr.length) * 100).toFixed(2);
      if (Number(this.uploadPercentage) > 99) {
        this.uploadPercentage = 99;
      }
      if (fileType !== 'gltf') {
        this.nocontainer = this.$t('oralReport.oralReport_1') + this.uploadPercentage + '%';
      }
    },

    loadOBJ(url, typeName, mtlUrl, picUrl, resolve) {
      const mtlLoader = new MTLLoader();

      if (mtlUrl && picUrl) {
        mtlLoader.load(mtlUrl, (materials) => {
          // 将材质库中的材质替换成Three.js的材质
          materials.preload();
          const objLoader = new OBJLoader();
          objLoader.setMaterials(materials);
          objLoader.load(
            url,
            (geometry) => {
              if (picUrl) {
                const textureLoader = new THREE.TextureLoader();
                let texture = textureLoader.load(picUrl);
                geometry.traverse((node) => {
                  if (node.material) {
                    node.material.side = THREE.DoubleSide; // 设置材质为双面
                    node.material.transparent = false;
                    node.material.opacity = 1;
                    node.material.depthWrite = true;

                    if (node.geometry instanceof THREE.BufferGeometry) {
                      node.geometry.computeVertexNormals();
                      node.geometry.attributes.normal.needsUpdate = true;
                    }
                    node['typeName'] = typeName;
                    node['texture'] = texture;
                  }
                });
                geometry.texture = texture;

                this[typeName] = geometry;
                this.groups.push(geometry);
                this.switchTexture(this.currtexturIndex, this.colorVal / 1000);

                resolve();
              } else {
                this.groups.push(geometry);

                this[typeName] = geometry;
                this[typeName].traverse((node) => {
                  if (node.material) {
                    // node.material.transparent = true;
                    this.materialData = node.material;
                    this.materialData.side = THREE.DoubleSide;
                    node['typeName'] = typeName;
                  }
                });
                resolve();
              }
            },
            (event) => {
              this.calculateProgress(typeName, event, 'obj');
            },
            (onerror) => {
              this.percentageBool = false;
              this.isModel = false;
              console.log('err----------->', onerror);
            },
          );
        });
      } else {
        objLoader.load(
          url,
          (geometry) => {
            // scene.add(geometry);
            this.groups.push(geometry);
            this[typeName] = geometry;

            this[typeName].traverse((node) => {
              if (node.material) {
                // node.material.transparent = false;
                // this.materialData = node.material;
                // this.materialData.side = THREE.DoubleSide;

                if (node.geometry instanceof THREE.BufferGeometry) {
                  node.geometry.computeVertexNormals();
                  node.geometry.attributes.normal.needsUpdate = true;
                }
              }
            });
            resolve();
          },
          (event) => {
            this.calculateProgress(typeName, event, 'obj');
          },
          (onerror) => {
            this.percentageBool = false;
            this.isModel = false;
            this.modelLoadingError = true;
            console.log('err----------->', onerror);
          },
        );
      }
    },
    async enterUndercutFun() {
      this.undercutPaintBtnDis = false;
      if (!this.isPrepareUndercut) {
        console.error('请先加载上颌或下颌模型');
        return false;
      }
      const enteUndercut = () => {
        this.isOpenUndercut = true;
        let typeNames = [this.$t('add.add164'), this.$t('add.add165')];
        this.viewAngle = 0;
        {
          this.currentClickType = 'undercut';
          this.fileListArr = this.fileListArr.filter((f) => typeNames.includes(f.typeName));
          this.fileListArr.forEach((item) => (item.show = true));

          if (this.group) {
            this.group.children.map((child) => {
              if (child.isMesh) {
                if (![...upperLangeNames, ...lowerLangeNames].includes(child.typeName)) {
                  child.visible = false;
                } else {
                  child.visible = true;
                }
              }
            });
          }
          if (!UnderCutionUpperSetDataOK || !UnderCutionUpperSetDataOK) {
            this.setDataForUnderCut();
            this.addMouseListeners(); // 添加鼠标监听器
          } else {
            this.isOpenUndercut = true;
            // this.onDBclickUndercut();
          }
        }
      };
      this.showMouseNum = false;
      //点击倒凹,取消咬合和口内相机
      if (this.currentClickType == 'occlu') {
        this.exitOcclusalFun();
        this.currentClickType = 'undercut';
        enteUndercut();
      } else if (this.currentClickType == 'Icamera') {
				this.exitOralFun()
        this.currentClickType = 'undercut';
        enteUndercut();
      } else {
        if (projectedUpperPoints?.length == 0 && projectedLowerPoints?.length == 0) {
          needsUpdateProjection = true;
          this.calcUndercutProjPts();
        }
        enteUndercut();
      }
    },

    async exitUndercutFun() {
      scene.remove(coneGeometryMesh);
      //取消倒凹
      this.isOpenUndercut = false;
      this.currentClickType = 'none';
      this.groups.forEach((mesh) => {
              const defValues = new Float32Array(mesh.geometry?.attributes?.position?.count).fill(0.0);
              mesh.geometry?.setAttribute('fHighlight', new THREE.BufferAttribute(defValues, 1));
              mesh.geometry?.setAttribute('disValue', new THREE.BufferAttribute(defValues, 1));
            });

      this.fileListArr = this.originalFileListArr;
      if (this.group) {
        this.group.children.map((child) => {
          child.visible = true;
        });
      }
    },
    async enterOcclusalFun() {
      if (!this.isPrepareOcclu) {
        console.error('请先加载上颌和下颌模型');
        return false;
      }
      if (this.currentClickType == 'Icamera') {
        this.exitOralFun();
      } else if (this.currentClickType == 'undercut') {
        this.exitUndercutFun();
      }
      this.loadingDialogKey = Math.random();
      this.loadingDialogShow = false;
      await this.$nextTick();
      this.calcLoading = true;
      this.calcLoadingText = this.$t('add.add1001');
      this.loadingDialogShow = true;
      this.viewAngle = '1';
      this.showMouseNum = false;

      setTimeout(async () => {
			
        let typeNames = [this.$t('add.add164'), this.$t('add.add165')];

        this.currentClickType = 'occlu';
        this.addMouseListeners(); // 添加鼠标监听器

        this.fileListArr = this.fileListArr.filter((f) => typeNames.includes(f.typeName));
        this.fileListArr.forEach((item) => (item.show = true));

        if (this.group) {
          this.group.children.map((child) => {
            if (child.isMesh) {
              if (![...upperLangeNames, ...lowerLangeNames].includes(child.typeName)) {
                child.visible = false;
              } else {
                child.visible = true;
              }
            }
          });
        }

        try {
          // 从初始化后的模块中获取OcclusionDetection类
          const OcclusionDetection = initializedModule.OcclusionDetection;

          // 创建OcclusionDetection实例
          const occlusion = new OcclusionDetection();
          const copyToWasm = (data) => {
            const numBytes = data.length * data.BYTES_PER_ELEMENT;
            const ptr = initializedModule._malloc(numBytes);
            const heap = new Uint8Array(initializedModule.HEAPU8.buffer, ptr, numBytes);
            heap.set(new Uint8Array(data.buffer));
            return ptr;
          };

          const uVPtr = copyToWasm(upperCasePosition);
          const uFPtr = copyToWasm(upperCaseIndices);
          const lVPtr = copyToWasm(lowerCasePosition);
          const lFPtr = copyToWasm(lowerCaseIndices);

          // 调用 setData 方法并检查返回值
          const setDataResult = occlusion.setData(
            uVPtr,
            upperCasePosition.length,
            uFPtr,
            upperCaseIndices.length,
            lVPtr,
            lowerCasePosition.length,
            lFPtr,
            lowerCaseIndices.length,
          );

          // 计算咬合距离
          upperDistancesOcclu = cloneDeep(occlusion.calUpperOcclusalDis());
          lowerDistancesOcclu = cloneDeep(occlusion.calLowerOcclusalDis());

          this.setOcclusionColor(upperDistancesOcclu, 'upper', this.colorVal / 1000);
          this.setOcclusionColor(lowerDistancesOcclu, 'lower', this.colorVal / 1000);
          uVPtr && initializedModule._free(uVPtr);
          uFPtr && initializedModule._free(uFPtr);
          lVPtr && initializedModule._free(lVPtr);
          lFPtr && initializedModule._free(lFPtr);
          this.calcLoading = false;
          this.loadingDialogShow = false;
        } catch (error) {
          console.error('Error in getOcclusalFun:', error);
          this.calcLoading = false;
          this.loadingDialogShow = false;

          this.$message.error('咬合检测功能暂时不可用');
          throw error;
        }
      }, 200);
    },
    async exitOcclusalFun() {
      this.showMouseNum = false;
      this.calcLoading = false;
      this.loadingDialogShow = false;

      if (this.viewAngle === '3') {
        setCamera(this, 1, 360, true);
      }
      this.currentClickType = 'none';
      this.groups.forEach((mesh) => {
        const defValues = new Float32Array(mesh.geometry?.attributes?.position?.count).fill(0.0);
        mesh.geometry?.setAttribute('fHighlight', new THREE.BufferAttribute(defValues,1));
        mesh.geometry?.setAttribute('disValue', new THREE.BufferAttribute(defValues, 1));
      });
      this.fileListArr = this.originalFileListArr;
      if (this.group) {
        this.group.children.map((child) => {
          child.visible = true;
        });
      }
		this.switchTexture(this.currtexturIndex, this.colorVal / 1000);
    },
  enterOralFun() {
   if (this.oralCameraDisplay === 'false' || this.intraoralCameraDisabled) {
     return false;
   }
   if (this.currentClickType == 'occlu') {
     this.showMouseNum = false;
 
     this.exitOcclusalFun();
   } else if (this.currentClickType == 'undercut') {
     this.exitUndercutFun();
     this.clearSmearHandle();
   }
   this.currentClickType = 'Icamera';
   this.viewfinderPic = '';
   let typeNames = [this.$t('add.add164'), this.$t('add.add165')];
   if (this.currentClickType == 'Icamera') {
     // 口内相机部分创建圆环 鼠标跟着移动
     this.createCircle();
     // 如果点击了口内相机就 只展示上下颚
     let showtypeNames = [];
     this.fileListArr = this.fileListArr.filter((f) => typeNames.includes(f.typeName));
     this.fileListArr.forEach((item, index) => {
       if (index === 0) {
         item['show'] = true;
         showtypeNames = [item.typeName];
       } else {
         item['show'] = false;
       }
     });
     this.toothFileListArr = this.fileListArr;
     this.group.traverse((child) => {
       if (child.isMesh) {
         if (!showtypeNames.includes(child.typeName)) {
           child.visible = false;
         } else {
           child.visible = true;
         }
       }
     });
     callback && callback();
   } else {
     callback && callback();
   }
 },
 exitOralFun() {
   this.currentClickType = 'none';
   this.boxes = [];
   this.fileListArr = this.originalFileListArr;
   this.group.traverse((child) => {
     if (child.isMesh) {
       child.visible = true;
     }
   });
 },
    getColor(colorVal) {
      this.colorVal = colorVal;
      if (this.currentClickType == 'occlu') {
        if (upperDistancesOcclu.length > 0 && lowerDistancesOcclu.length > 0) {
          this.setOcclusionColor(upperDistancesOcclu, 'upper', colorVal / 1000);
          this.setOcclusionColor(lowerDistancesOcclu, 'lower', colorVal / 1000);
        }
      } else if (this.currentClickType == 'undercut') {
        if (upperDistancesUndercut.length > 0) {
          this.setUndercutColor(upperDistancesUndercut, 'upper', colorVal / 1000);
        }
        if (lowerDistancesUndercut) {
          this.setUndercutColor(lowerDistancesUndercut, 'lower', colorVal / 1000);
        }
      }
    },

    setUndercutColor(UndercutDistances, direc = 'upper', distanceColor = 1.0) {
      // 检查必要的数据是否存在

      const currentLange = localStorage.getItem('lang') || 'cn'; // 提供默认值
      const meshUpperLange = {
        cn: upperLangeNames[0],
        en: upperLangeNames[1],
        pt: upperLangeNames[2],
      };
      const meshLowerLange = {
        cn: lowerLangeNames[0],
        en: lowerLangeNames[1],
        pt: lowerLangeNames[2],
      };

      const upperMeshKey = meshUpperLange[currentLange];
      const lowerMeshKey = meshLowerLange[currentLange];

      // 检查对象属性键是否存在
      if (!upperMeshKey && !lowerMeshKey) {
        console.warn('Invalid mesh keys for current language:', currentLange);
        return;
      }

      // 安全地访问对象属性
      const upperMesh = this[upperMeshKey];
      const lowerMesh = this[lowerMeshKey];

      // 检查模型对象是否存在
      if ((direc === 'upper' && !upperMesh) || (direc === 'lower' && !lowerMesh)) {
        console.warn('Upper mesh is not available', { upperMesh,
upperMeshKey });
        return;
      }

      if (direc === 'upper' && upperMesh) {
        this.currentMesh = upperMesh;
      } else if (direc === 'lower' && lowerMesh) {
        this.currentMesh = lowerMesh;
      } else {
        return;
      }
      if (this.isPrepareUndercut && this.currentClickType == 'undercut') {
        //当前咬合检测打开
        this.currentMesh.geometry.setAttribute(
          'disValue',
          new THREE.Float32BufferAttribute(UndercutDistances, 1),
        );
      }
      this.switchTexture(this.currtexturIndex, distanceColor);
      this.$nextTick(() => {
        this.calcLoading = false;
        // this.calcLoadingText=this.$t('add.add105');
        this.loadingDialogShow = false;
      });
    },
    setOcclusionColor(occlusionDistances, direc = 'upper', distanceColor = 1.0) {
      // 检查必要的数据是否存在

      const currentLange = localStorage.getItem('lang') || 'cn'; // 提供默认值
      const meshUpperLange = {
        cn: upperLangeNames[0],
        en: upperLangeNames[1],
        pt: upperLangeNames[2],
      };
      const meshLowerLange = {
        cn: lowerLangeNames[0],
        en: lowerLangeNames[1],
        pt: lowerLangeNames[2],
      };

      const upperMeshKey = meshUpperLange[currentLange];
      const lowerMeshKey = meshLowerLange[currentLange];

      // 检查对象属性键是否存在
      if (!upperMeshKey || !lowerMeshKey) {
        console.warn('Invalid mesh keys for current language:', currentLange);
        return;
      }

      // 安全地访问对象属性
      const upperMesh = this[upperMeshKey];
      const lowerMesh = this[lowerMeshKey];

      // 检查模型对象是否存在
      if (direc === 'upper' && !upperMesh) {
        console.warn('Upper mesh is not available', { upperMesh,
upperMeshKey });
        return;
      }

      if (direc === 'lower' && !lowerMesh) {
        console.warn('Lower mesh is not available', { lowerMesh,
lowerMeshKey });
        return;
      }

      if (direc === 'upper' && upperMesh) {
        this.currentMesh = upperMesh;
      } else if (direc === 'lower' && lowerMesh) {
        this.currentMesh = lowerMesh;
      } else {
        return;
      }
      if (this.isPrepareOcclu && this.currentClickType == 'occlu') {
        //当前咬合检测打开
        this.currentMesh.geometry.setAttribute(
          'disValue',
          new THREE.Float32BufferAttribute(occlusionDistances, 1),
        );
      }
      this.switchTexture(this.currtexturIndex, distanceColor, 1);
    },

    loadGltf(url, binUrl, picUrl, typeName, resolve) {
      const dracoLoader = new DRACOLoader();
      dracoLoader.setDecoderPath('https://oss.blzdentalcloud.com/threejs/');
      const loader = new GLTFLoader();
      loader.manager.setURLModifier((o_url) => {
        // 检查 URL 是否是一个 bin 文件
        if (o_url.endsWith('.bin')) {
          if (o_url.indexOf('upperjaw.') !== -1) {
            o_url = this.fileListArr.filter((f) => f.mtlUrl.indexOf('upperjaw.') !== -1)[0].mtlUrl;
          }
          if (o_url.indexOf('lowerjaw.') !== -1) {
            o_url = this.fileListArr.filter((f) => f.mtlUrl.indexOf('lowerjaw.') !== -1)[0].mtlUrl;
          }
          if (o_url.indexOf('upperjaw-situ.') !== -1) {
            o_url = this.fileListArr.filter((f) => f.mtlUrl.indexOf('upperjaw-situ.') !== -1)[0]
              .mtlUrl;
          }
          if (o_url.indexOf('lowerjaw-situ.') !== -1) {
            o_url = this.fileListArr.filter((f) => f.mtlUrl.indexOf('lowerjaw-situ.') !== -1)[0]
              .mtlUrl;
          }
          if (o_url.indexOf('upperjaw-marker.') !== -1) {
            o_url = this.fileListArr.filter((f) => f.mtlUrl.indexOf('upperjaw-marker.') !== -1)[0]
              .mtlUrl;
          }
          if (o_url.indexOf('lowerjaw-marker.') !== -1) {
            o_url = this.fileListArr.filter((f) => f.mtlUrl.indexOf('lowerjaw-marker.') !== -1)[0]
              .mtlUrl;
          }
        }
        return o_url;
      });
      loader.setDRACOLoader(dracoLoader);
      // 需要根据实际的资源路径调整文件路径
      loader.load(
        url,
        (gltf) => {
          let geometry = gltf.scene;
          let texture;
          geometry.traverse((child) => {
            if (child.isMesh) {
              child.geometry.computeVertexNormals();
              const material = child.material;

              if (material.map) {
                material.map.encoding = THREE.LinearEncoding;
                texture = material.map;
                child.ctmtexture = texture;

                material.map.needsUpdate = true;
              }
              if (material.emissiveMap) {
                // 如果有放射光纹理，也需要设置为线性
                material.emissiveMap.encoding = THREE.LinearEncoding;
                material.emissiveMap.needsUpdate = true;
              }

              if (material.roughnessMap) {
                material.roughnessMap.encoding = THREE.LinearEncoding;
                material.roughnessMap.needsUpdate = true;
              }

              material.transparent = false;
              material.opacity = 1;
              // 如果有 Alpha 通道，确保启用深度写入
              material.depthWrite = true;
              material.side = THREE.DoubleSide;
              this.modelType = this.previewType;

              if (this.previewType === 'stl') {
                this.currtexturIndex = '1';

                if (child.geometry instanceof THREE.BufferGeometry) {
                  child.geometry.computeVertexNormals();
                  child.geometry.attributes.normal.needsUpdate = true;
                }
              } else {
                this.currtexturIndex = '3';
                if (child.geometry instanceof THREE.BufferGeometry) {
                  child.geometry.computeVertexNormals();
                  child.geometry.attributes.normal.needsUpdate = true;
                }
              }
              child.texture = texture;
              child['typeName'] = typeName;
            }
          });
          this.groups.push(geometry);
          this.switchTexture(this.currtexturIndex, this.colorVal / 1000);

          this[typeName] = geometry;
          this[typeName].traverse((node) => {
            if (node.material) {
              if (node.geometry instanceof THREE.BufferGeometry) {
                node.geometry.computeVertexNormals();
                node.geometry.attributes.normal.needsUpdate = true;
              }
            }
          });

          resolve();
        },
        (event) => {
          this.nocontainer = this.$t('oralReport.oralReport_1') + '99%';
        }, // 进度回调
        (error) => {
          this.modelLoadingError = true;
          this.percentageBool = false;
          console.error('加载GLTF模型失败', error);
        },
      );
    },

    async loadBlzmesh(url, typeName, resolve) {
      this.$http({
        method: 'get',
        url: url,
        withCredentials: false,
        responseType: 'arraybuffer',
      })
        .then(async (res) => {
          const data = await this.handleBlzmeshData(res.data, typeName, url);
          const mesh = data.mesh;

          if ([...upperLangeNames, ...lowerLangeNames].includes(typeName)) {
            const originalGeometry = mesh.geometry.clone();
            const mergedGeometry = originalGeometry;
            originalGeometry.dispose();
            // 替换为去重后的几何体
            mesh.geometry = mergedGeometry;
          }

          // for undercut.wasm or occlu.wasm setData
          const geometry = mesh.geometry;
          if ([...upperLangeNames, ...lowerLangeNames].includes(typeName)) {
            // 直接使用去重后的几何体属性
            const positions = geometry.attributes.position.array;
            const indexs = geometry.index.array;

            if (upperLangeNames.includes(typeName)) {
              // 取索引的前三分之一
              upperCaseIndices = indexs;
              upperCasePosition = positions; // 去重后的顶点位置
            } else {
              // 取索引的前三分之一
              lowerCaseIndices = indexs;
              lowerCasePosition = positions;
            }
          }

          this.blzmeshMeshs.push(mesh);
          this[typeName] = mesh;
          this.groups.push(mesh);

          this.switchTexture(this.currtexturIndex, this.colorVal / 1000);
          resolve();
        })
        .catch((error) => {
          console.error('加载BLZMESH模型失败', error);
          this.modelLoadingError = true;
          this.percentageBool = false;
        });
    },

    TransformationalModel(type) {
      if (type === 'obj') {
        // 导出为OBJ格式
        this.blzmeshMeshs.forEach((mesh) => {
          // 创建OBJ导出器
          const objExporter = new OBJExporter();
          // 使用 removeDuplicateVertex 方法去除重复顶点
          // let deleted = this.removeDuplicateVertex(mesh);

          const geometry = mesh.geometry;
          // 获取顶点颜色
          const colors = geometry.attributes.color ? geometry.attributes.color.array : null;

          // 导出处理后的geometry
          let objData = objExporter.parse(new THREE.Mesh(geometry));

          // 确保OBJ数据包含顶点颜色信息
          if (geometry.attributes.color) {
            const vertices = geometry.attributes.position.array;
            const colors = geometry.attributes.color.array;
            let coloredObjData = '';

            // 遍历每个顶点，添加颜色信息
            for (let i = 0; i < vertices.length; i += 3) {
              coloredObjData += `v ${vertices[i]} ${vertices[i + 1]} ${vertices[i + 2]} ${
                colors[i]
              } ${colors[i + 1]} ${colors[i + 2]}\n`;
            }

            // 替换原始OBJ数据中的顶点数据
            const otherData = objData
              .split('\n')
              .filter((line) => !line.startsWith('v '))
              .join('\n');
            objData = coloredObjData + otherData;
          }

          // 创建材质内容，引用贴图
          const mtlContent = `newmtl material_0
            Ka 0.200000 0.200000 0.200000
            Kd 1.000000 1.000000 1.000000
            Ks 1.000000 1.000000 1.000000
            Tr 1.000000
            illum 2
            Ns 0.000000
            map_Kd texture.png`;

          // 从着色器材质中获取贴图数据
          let textureData;
          mesh.traverse((node) => {
            if (node.material && node.material.uniforms && node.material.uniforms.uTexture) {
              const texture = node.material.uniforms.uTexture.value;
              const canvas = document.createElement('canvas');
              const ctx = canvas.getContext('2d');
              canvas.width = texture.image.width;
              canvas.height = texture.image.height;
              ctx.drawImage(texture.image, 0, 0);

              canvas.toBlob((blob) => {
                textureData = blob;
                // 创建包含OBJ、MTL和贴图的压缩包
                const zip = new JSZip();
                zip.file('model.obj', objData);
                zip.file('model.mtl', mtlContent);

                if (textureData) {
                  zip.file('texture.png', textureData);
                }

                // 在OBJ文件中添加MTL引用
                const objWithMtl = `mtllib model.mtl\n${objData}`;
                zip.file('model.obj', objWithMtl);

                // 生成并下载压缩包
                zip.generateAsync({ type: 'blob' }).then((content) => {
                  const zipUrl = URL.createObjectURL(content);
                  const downloadLink = document.createElement('a');
                  downloadLink.href = zipUrl;
                  downloadLink.download = 'model.zip';
                  downloadLink.click();
                });
              }, 'image/png');
            }
          });
        });
      } else if (type === 'ply') {
        this.blzmeshMeshs.forEach((mesh) => {
          const geometry = mesh.geometry;
          const colors = geometry.attributes.color ? geometry.attributes.color.array : null;

          // 如果有顶点颜色，将红色和蓝色通道交换
          if (colors) {
            for (let i = 0; i < colors.length; i += 3) {
              colors[i] /= 255; // 红色通道
              colors[i + 1] /= 255; // 绿色通道
              colors[i + 2] /= 255; // 蓝色通道
            }
          }
          geometry.attributes.color.needsUpdate = true;
          const exporter = new PLYExporter();
          let plyData = exporter.parse(new THREE.Mesh(geometry));

          // 从着色器材质中获取贴图数据
          let textureData;
          mesh.traverse((node) => {
            if (node.material && node.material.uniforms && node.material.uniforms.uTexture) {
              const texture = node.material.uniforms.uTexture.value;
              const canvas = document.createElement('canvas');
              const ctx = canvas.getContext('2d');
              canvas.width = texture.image.width;
              canvas.height = texture.image.height;
              ctx.drawImage(texture.image, 0, 0);

              canvas.toBlob((blob) => {
                textureData = blob;
                const zip = new JSZip();
                if (textureData) {
                  zip.file('texture.png', textureData);
                  plyData = plyData.replace(
                    'end_header',
                    'comment TextureFile: texture.png\nend_header',
                  );
                }

                const plyBlob = new Blob([plyData], { type: 'text/plain' });

                zip.file('model.ply', plyBlob);
                // 生成并下载压缩包
                zip.generateAsync({ type: 'blob' }).then((content) => {
                  const zipUrl = URL.createObjectURL(content);
                  const downloadLink = document.createElement('a');
                  downloadLink.href = zipUrl;
                  downloadLink.download = 'model.zip';
                  downloadLink.click();
                });
              }, 'image/png');
            }
          });
        });
      } else if (type === 'stl') {
        this.blzmeshMeshs.forEach((mesh) => {
          const geometry = mesh.geometry;
          // if (geometry.attributes.color) {
          //   const colors = geometry.attributes.color.array;
          //   for (let i = 0; i < colors.length; i += 3) {
          //     colors[i] /= 255;     // 红色通道
          //     colors[i + 1] /= 255; // 绿色通道
          //     colors[i + 2] /= 255; // 蓝色通道
          //   }
          //   geometry.attributes.color.needsUpdate = true;
          // }

          const exporter = new STLExporter();
          let stlData = exporter.parse(mesh);

          // 从着色器材质中获取贴图数据
          let textureData;
          mesh.traverse((node) => {
            if (node.material && node.material.uniforms && node.material.uniforms.uTexture) {
              const texture = node.material.uniforms.uTexture.value;
              const canvas = document.createElement('canvas');
              const ctx = canvas.getContext('2d');
              canvas.width = texture.image.width;
              canvas.height = texture.image.height;
              ctx.drawImage(texture.image, 0, 0);

              canvas.toBlob((blob) => {
                textureData = blob;
                const zip = new JSZip();
                if (textureData) {
                  zip.file('texture.png', textureData);
                  // Add texture information to the STL data
                  stlData = stlData.replace(
                    'endsolid',
                    'comment TextureFile: texture.png\nendsolid',
                  );
                }

                const stlBlob = new Blob([stlData], { type: 'text/plain' });
                zip.file('model.stl', stlBlob);
                // 生成并下载压缩包
                zip.generateAsync({ type: 'blob' }).then((content) => {
                  const zipUrl = URL.createObjectURL(content);
                  const downloadLink = document.createElement('a');
                  downloadLink.href = zipUrl;
                  downloadLink.download = 'model.zip';
                  downloadLink.click();
                });
              }, 'image/png');
            }
          });
        });
      } else if (type === 'gltf') {
        this.blzmeshMeshs.forEach((item) => {
          // 导出为GLTF格式
          const gltfExporter = new GLTFExporter();
          gltfExporter.parse(
            item,
            (gltfData) => {
              const gltfBlob = new Blob([JSON.stringify(gltfData)], { type: 'application/json' });
              const gltfUrl = URL.createObjectURL(gltfBlob);
              const gltfLink = document.createElement('a');
              gltfLink.href = gltfUrl;
              gltfLink.download = 'model.gltf';
              gltfLink.click();
            },
            { binary: false },
          );
        });
      }

      this.$message({
        message: '导出成功',
        type: 'success',
      });
    },
    /*************  ✨ Codeium Command 🌟  *************/
    async loadTexture(imgBuffer) {
      const blob = new Blob([imgBuffer], { type: 'image/png' });
      const url = URL.createObjectURL(blob);

      return new Promise((resolve, reject) => {
        const texture = new THREE.TextureLoader().load(
          url,
          () => {
            resolve(texture);
          },
          undefined,
          (error) => {
            reject(error);
          },
        );
      });
    },

    async handleBlzmeshData(arrayBuffer, typeName, url) {
      // 解析 blzmesh 文件数据
      const dataView = new DataView(arrayBuffer);

      let offset = 0;

      // 读取并验证文件标识符 "blz-2.0"
      const identifier = new TextDecoder().decode(new Uint8Array(arrayBuffer, 0, 7));
      if (identifier !== 'blz-2.0') {
        console.error('文件标识符不正确');
        return false;
      }
      offset += 7;

      // 读取缓存数据大小和图像数据大小
      const bufferSize = Number(dataView.getBigInt64(offset, true));
      offset += 8;
      const imgBufferSize = Number(dataView.getBigInt64(offset, true));
      offset += 8;

      // 提取网格数据
      const meshBuffer = arrayBuffer.slice(offset, offset + bufferSize);
      offset += bufferSize;

      // 提取图像数据(如果存在)
      let textureData = null;
      if (imgBufferSize > 0) {
        const imgBuffer = arrayBuffer.slice(offset, offset + imgBufferSize);
        try {
          // 加载纹理
          const texture = await this.loadTexture(imgBuffer);

          textureData = texture;
        } catch (error) {
          console.error('纹理加载失败:', error);
        }
      }

      // 解码网格数据
      try {
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath('https://oss.blzdentalcloud.com/threejs/');

        // 使用 Promise 包装 Draco 解码过程
        let geometry = await new Promise((resolve, reject) => {
          dracoLoader.decodeDracoFile(
            meshBuffer,
            (geometry) => {
              resolve(geometry);
            },
            null,
            null,
            reject,
          );
        });

        const isScanRod = scanRodLastName.some((item) => url.endsWith(item)); //判断是否是扫描杆
        // 计算法线
        geometry.computeVertexNormals();
        geometry.attributes.normal.needsUpdate = true;
        if (isScanRod) {
          const CalculateRenderData = initializedModule.CalculateRenderData;

          // 创建OcclusionDetection实例
          const CalculateRenderDataTion = new CalculateRenderData();
          const copyToWasm = (data) => {
            const numBytes = data.length * data.BYTES_PER_ELEMENT;
            const ptr = initializedModule._malloc(numBytes);
            const heap = new Uint8Array(initializedModule.HEAPU8.buffer, ptr, numBytes);
            heap.set(new Uint8Array(data.buffer));
            return ptr;
          };

          const positions = geometry.attributes.position.array;
          const indexs = geometry.index.array;

          const uVPtr = copyToWasm(positions);
          const uFPtr = copyToWasm(indexs);
          // 对于扫描杆，使用CalculateRenderDataTion来处理几何数据
          CalculateRenderDataTion.setData(uVPtr, positions.length, uFPtr, indexs.length);
          try {
            // 获取新的顶点、法向量和面数据
            const newVertData = CalculateRenderDataTion.getVertData();
            const newNormalData = CalculateRenderDataTion.getNormalData();
            const newFaceData = CalculateRenderDataTion.getFaceData();

            // 设置顶点位置属性
            if (newVertData && newVertData.length > 0) {
              geometry.setAttribute(
                'position',
                new THREE.BufferAttribute(new Float32Array(newVertData), 3),
              );
              geometry.computeVertexNormals();
            }

            // 设置法向量属性
            if (newNormalData && newNormalData.length > 0) {
              geometry.attributes.normal = new THREE.BufferAttribute(
                new Float32Array(newNormalData),
                3,
              );
            }
            geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(newFaceData), 1));
          } catch (error) {
            console.error('处理扫描杆数据时出错:', error);
            // 如果处理失败，继续使用原始几何体
          }
        }
        const defaultColorValue = isScanRod
          ? new THREE.Vector3(150.0 / 255.0, 150.0 / 255.0, 154.0 / 255.0)
          : new THREE.Vector3(231.0 / 255.0, 223.0 / 255.0, 147.0 / 255.0);
        if (this.previewType === 'stl') {
          this.currtexturIndex = '1';
        } else {
          this.currtexturIndex = '3';
        }

        const mesh = new THREE.Mesh(geometry);

        mesh.texture = textureData;

        mesh['typeName'] = typeName;
        mesh['meshUrl'] = url;
        return {
          mesh,
          geometry,
        };
      } catch (error) {
        console.error('网格解码失败:', error);
        return null;
      }
    },
    calcUndercutProjPts() {
      if (!needsUpdateProjection) return;
      needsUpdateProjection = false;

      if (this.blzmeshMeshs && this.blzmeshMeshs.length > 0) {
        // 检查是否已经创建了Worker，如果没有则创建
        if (!this.projectionWorker) {
          this.projectionWorker = new Worker('/preview-worker.js');

          // 监听Worker消息
          this.projectionWorker.onmessage = (event) => {
            const { type, ...data } = event.data;

            if (type === 'processed') {
              // 接收 Worker 处理过的数据并执行 WASM 计算
              this.executeWasmCalculations(data.processedMeshData, data.cameraData, data.viewport);
            } else if (type === 'error') {
              console.error('Worker计算错误:', data.message);
            }
          };
        }

        // 准备要发送到Worker的数据
        const meshData = this.blzmeshMeshs.map((mesh) => ({
          typeName: mesh.typeName,
          positions: Array.from(mesh.geometry.attributes.position.array),
          matrixWorld: mesh.matrixWorld.elements,
        }));

        const cameraData = {
          matrixWorldInverse: this.camera.matrixWorldInverse.elements,
          projectionMatrix: this.camera.projectionMatrix.elements,
        };

        let viewport = this.renderer.getViewport(new THREE.Vector4());
        const viewportArray = [viewport.x, viewport.y, viewport.width, viewport.height];

        // 发送计算请求到Worker
        this.projectionWorker.postMessage({
          type: 'calculate',
          meshData,
          cameraData,
          viewport: viewportArray,
          upperLangeNames,
          lowerLangeNames,
        });
      }
    },

    executeWasmCalculations(processedMeshData, cameraData, viewport) {
      // 在主线程中执行实际的 WASM 计算
      processedMeshData.forEach((mesh) => {
        const currentMesh = this.blzmeshMeshs.find((item) => mesh.typeName == item.typeName);
        if (mesh.isUpper) {
          // 写入原始顶点数据到共享内存
          const positions = mesh.positions;
          let orginUpperPtsPtr = writeFlatVec3ArrayToMemory(Array.from(positions), wasmPtsMemory);

          const mvpInfo = {};
          mvpInfo.M = mesh.matrixWorld; // 模型矩阵
          mvpInfo.V = cameraData.matrixWorldInverse; // 视图矩阵
          mvpInfo.P = cameraData.projectionMatrix; // 投影矩阵
          mvpInfo.VP = new Float32Array(viewport);
          updateProjectedPoints(this, currentMesh, projectedUpperPoints);

          projectedUpperPoints = projectMesh2PointsInGLCoordHandle(
            WebAssembly,
            wasmPtsMemory,
            orginUpperPtsPtr,
            mvpInfo,
          );
        } else if (mesh.isLower) {
          // 写入原始顶点数据到共享内存
          const positions = mesh.positions;
          let orginLowerPtsPtr = writeFlatVec3ArrayToMemory(Array.from(positions), wasmPtsMemory);

          const mvpInfo = {};
          mvpInfo.M = mesh.matrixWorld; // 模型矩阵
          mvpInfo.V = cameraData.matrixWorldInverse; // 视图矩阵
          mvpInfo.P = cameraData.projectionMatrix; // 投影矩阵
          mvpInfo.VP = new Float32Array(viewport);

          updateProjectedPoints(this, currentMesh, projectedLowerPoints);

          projectedLowerPoints = projectMesh2PointsInGLCoordHandle(
            WebAssembly,
            wasmPtsMemory,
            orginLowerPtsPtr,
            mvpInfo,
          );
        }
      });
    },

    // 在组件的methods中添加清理方法
    cleanupWorkers() {
      if (this.projectionWorker) {
        this.projectionWorker.terminate();
        this.projectionWorker = null;
      }
    },
    calcUndercutProjPts2() {
      if (!needsUpdateProjection) return;
      needsUpdateProjection = false;

      if (this.blzmeshMeshs && this.blzmeshMeshs.length > 0) {
        // 分批处理网格以避免阻塞主线程
        const batchSize = 2; // 每批处理的网格数量
        let currentIndex = 0;

        const processBatch = () => {
          const batchEnd = Math.min(currentIndex + batchSize, this.blzmeshMeshs.length);

          for (let i = currentIndex; i < batchEnd; i++) {
            const mesh = this.blzmeshMeshs[i];

            if (upperLangeNames.includes(mesh.typeName)) {
              // 写入原始顶点数据到共享内存
              const positions = mesh.geometry.attributes.position.array;
              let orginUpperPtsPtr = writeFlatVec3ArrayToMemory(
                Array.from(positions),
                wasmPtsMemory,
              );

              const mvpInfo = {};
              mvpInfo.M = mesh.matrixWorld.elements;
              mvpInfo.V = this.camera.matrixWorldInverse.elements;
              mvpInfo.P = this.camera.projectionMatrix.elements;
              let viewport = this.renderer.getViewport(new THREE.Vector4());
              mvpInfo.VP = new Float32Array([
                viewport.x,
                viewport.y,
                viewport.width,
                viewport.height,
              ]);
              updateProjectedPoints(this, mesh, projectedUpperPoints);

              projectedUpperPoints = projectMesh2PointsInGLCoordHandle(
                WebAssembly,
                wasmPtsMemory,
                orginUpperPtsPtr,
                mvpInfo,
              );
            } else if (lowerLangeNames.includes(mesh.typeName)) {
              // 写入原始顶点数据到共享内存
              const positions = mesh.geometry.attributes.position.array;
              let orginLowerPtsPtr = writeFlatVec3ArrayToMemory(
                Array.from(positions),
                wasmPtsMemory,
              );

              const mvpInfo = {};
              mvpInfo.M = mesh.matrixWorld.elements;
              mvpInfo.V = this.camera.matrixWorldInverse.elements;
              mvpInfo.P = this.camera.projectionMatrix.elements;
              let viewport = this.renderer.getViewport(new THREE.Vector4());
              mvpInfo.VP = new Float32Array([
                viewport.x,
                viewport.y,
                viewport.width,
                viewport.height,
              ]);
              updateProjectedPoints(this, mesh, projectedLowerPoints);
              projectedLowerPoints = projectMesh2PointsInGLCoordHandle(
                WebAssembly,
                wasmPtsMemory,
                orginLowerPtsPtr,
                mvpInfo,
              );
            }
          }

          currentIndex = batchEnd;

          // 如果还有剩余网格需要处理，则在下一个事件循环中继续处理
          if (currentIndex < this.blzmeshMeshs.length) {
            setTimeout(processBatch, 10);
          }
        };

        // 开始处理第一批次
        processBatch();
      }
    },
    render() {
      this.animationFrameId = requestAnimationFrame(this.render);
      if (this.camera.position) {
        var vector = this.camera.position.clone();
      }
      this.point.position.set(vector.x, vector.y, vector.z); // 点光源位置
      controls.update();
      this.renderer.render(scene, this.camera);
    },

    // 更新渲染器操作
    updateRender() {
      const element = document.getElementById('container');
      if (element) {
        const aspect = element.clientWidth / element.clientHeight;
        const cameraWidth = this.originalSize.x * 1.5; // 根据原始尺寸的一半
        const cameraHeight = cameraWidth / aspect; // 维持宽高比

        this.camera.left = -cameraWidth;
        this.camera.right = cameraWidth;
        this.camera.top = cameraHeight;
        this.camera.bottom = -cameraHeight;
        this.camera.updateProjectionMatrix();
        let boundingBox = new THREE.Box3().setFromObject(this.group);
        let center = new THREE.Vector3();
        boundingBox.getCenter(center);
        this.group.position.sub(center);
        this.renderer.setPixelRatio(window.devicePixelRatio * 1); // 更新渲染器的像素比
        // 更新渲染器的大小
        this.renderer.setSize(element.clientWidth, element.clientHeight);
      }
    },

    modelSwitch(tagItem) {
      // this[tagItem.typeName].visible = tagItem.show;
      if (this.currentClickType == 'Icamera') {
        this.fileListArr.map((f) => (f['show'] = false));
        tagItem['show'] = true;
        this.getConfigTxt(tagItem);
      }
      this.group.traverse((child) => {
        if (child.isMesh) {
          if (child.typeName === tagItem.typeName) {
            child.visible = tagItem.show;
          } else {
            if (this.currentClickType == 'Icamera') {
              child.visible = false;
            }
          }
        }
      });
    },

    fullScreen() {
      this.isFullScreen = !this.isFullScreen;
      window.parent.postMessage(
        {
          type: 'toFullScreen',
          data: this.isFullScreen,
        },
        '*',
      );
    },
    // 点击重置角度
    resetAngle() {
      controls.reset();
    },
    switchView(type) {
      if (this.viewAngle == type) {
        return false;
      }
      controls.reset();
      this.viewAngle = type;
      if (type === '1') {
        setCamera(this, 1, 360, true);
      } else if (type === '3') {
        setCamera(this, 0.3, -90, false);
      }
    },

    formatTooltip(val) {
      return val + '%';
    },

    toLoadModel() {
      if (this.previewOrder?.archiveStatus == 0) {
        this.$message({
          message: this.$t('caseorder.yulan.yulan141'),
          type: 'error',
        });
        return;
      }
      this.percentageBool = true;
      this.nocontainer = this.$t('caseorder.yulan.yulan139');
      const { markStatus, backMarkStatus } = this.previewOrder;
      if (
        (this.orderManagement == 'false' && markStatus === 2) ||
        (this.orderManagement == 'true' && backMarkStatus === 2)
      ) {
        this.nocontainer = this.$t('caseorder.yulan.yulan140');
      }
      const _orderId = this.id ? this.id : this.$route.query.id;
      this.$http
        .get('/archive/order/restore?orderId=' + _orderId)
        .then(({ data: res }) => {
          if (res.code !== 0) {
            this.percentageBool = false;
            this.$message({
              message: res.msg || this.$t('caseorder.yulan.yulan141'),
              type: 'error',
            });
          } else {
            this.percentageBool = true;

            // this.initModel();
          }
        })
        .catch((err) => {
          console.log('err', err);
        });
    },

    picAction(type) {
      if (this.isInvalid) {
        return false;
      }
      this.brightnessIndex = type;
      if (type === '1') {
        if (this.brightness <= 1.5) {
          this.brightness += 0.05;
        }
      } else if (type === '2') {
        if (this.brightness >= 0.5) {
          this.brightness -= 0.05;
        }
      } else if (type === '3') {
        this.picBoxFull = !this.picBoxFull;
        if (this.viewfinderPic) {
          window.parent.postMessage(
            {
              type: 'setPicBoxFull', // 自定义事件的类型
              data: {
                picBoxFull: this.picBoxFull,
                viewfinderPic: this.viewfinderPic,
                brightness: this.brightness,
              },
            },
            '*',
          );
        }
      }
    },
  },
  watch: {
    '$i18n.locale'(val) {
      this.isCurrentLang = val;
    },
    '$store.state.orderDetailOrder'(newVal) {
      if (newVal.order) {
        if (newVal.order.archiveStatus === 0) {
          this.$message({
            message: this.$t('caseorder.yulan.yulan141'),
            type: 'error',
          });
          return;
        }
      }
    },
  },
  beforeDestroy() {
    this.cleanupWorkers();
    disposeAll(scene, controls, this.camera, renderer);
    // 取消动画循环
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
    }

    // 清理鼠标移动定时器
    if (this.mouseMoveTimerOccluOcclu) {
      clearTimeout(this.mouseMoveTimerOcclu);
    }

    // 移除事件监听器
    if (this.renderer && this.renderer.domElement) {
      const domElement = this.renderer.domElement;
      domElement.removeEventListener('mousemove', this.onMouseMoveOcclu);
      domElement.removeEventListener('mouseenter', this.onMouseEnterOcclu);
      domElement.removeEventListener('mouseleave', this.onMouseLeaveOcclu);
    }

    // 清理渲染器
    if (this.renderer) {
      this.renderer.dispose();
      this.renderer.forceContextLoss();
    }

    // 清理场景中的对象
    scene.traverse((object) => {
      if (object.geometry) object.geometry.dispose();
      if (object.material) {
        if (object.material.map) object.material.map.dispose();
        object.material.dispose();
      }
    });
    scene = null;
    this.camera = null;
    controls = null;
  },
};
</script>
